From 277cbca878b48a997f089c7e070cdce74c3284bb Mon Sep 17 00:00:00 2001 From: Panda RPKI OSS Local Date: Wed, 9 Sep 2026 18:01:15 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=20Panda=20RPKI=20v0?= =?UTF-8?q?.1.0=20=E5=BC=80=E6=BA=90=E5=80=99=E9=80=89=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 13 + .github/workflows/ci.yml | 32 + .gitignore | 10 + CHANGELOG.md | 13 + CONTRIBUTING.md | 14 + Cargo.lock | 2133 + Cargo.toml | 34 + LICENSE | 29 + README.md | 68 + THIRD_PARTY_NOTICES.md | 239 + docker/Dockerfile | 22 + docker/compose.all5.yaml | 62 + docker/compose.daemon.yaml | 40 + docker/compose.yaml | 28 + docs/command-line-reference.md | 139 + docs/conformance-matrix.md | 21 + docs/development.md | 33 + docs/docker.md | 84 + docs/getting-started.md | 66 + docs/third-party-licenses.txt | 42111 ++++++++++++++++ docs/trust-anchors.md | 148 + docs/usage.md | 101 + src/ccr/accumulator.rs | 245 + src/ccr/build.rs | 266 + src/ccr/compare_view.rs | 235 + src/ccr/decode.rs | 541 + src/ccr/encode.rs | 303 + src/ccr/export.rs | 25 + src/ccr/hash.rs | 9 + src/ccr/manifest_location.rs | 425 + src/ccr/mod.rs | 34 + src/ccr/model.rs | 398 + src/ccr/projection.rs | 69 + src/ccr/verify.rs | 359 + src/cli/mod.rs | 226 + src/cli/validate.rs | 276 + src/daemon/metrics.rs | 78 + src/daemon/mod.rs | 374 + src/daemon/process.rs | 104 + src/daemon/storage.rs | 46 + src/daemon/tests.rs | 113 + src/lib.rs | 23 + src/logging.rs | 335 + src/logging/progress.rs | 72 + src/main.rs | 16 + src/model/aspa.rs | 412 + src/model/common.rs | 289 + src/model/crl.rs | 537 + src/model/manifest.rs | 976 + src/model/mod.rs | 13 + src/model/oid.rs | 78 + src/model/projection.rs | 663 + src/model/rc.rs | 30 + src/model/rc/certificate_validation.rs | 248 + src/model/rc/parsed_validation.rs | 346 + src/model/rc/parsing.rs | 584 + src/model/rc/strict_name_tests.rs | 102 + src/model/rc/types.rs | 564 + src/model/roa.rs | 631 + src/model/router_cert.rs | 365 + src/model/signed_object.rs | 27 + src/model/signed_object/cms_reader.rs | 172 + src/model/signed_object/parsed_profile.rs | 542 + src/model/signed_object/signed_attrs.rs | 344 + src/model/signed_object/signed_object_impl.rs | 118 + src/model/signed_object/tests.rs | 117 + src/model/signed_object/types_errors.rs | 300 + src/model/ta.rs | 312 + src/model/tal.rs | 183 + src/output/analysis/mod.rs | 1 + src/output/analysis/timing.rs | 353 + src/output/audit.rs | 336 + src/output/audit_downloads.rs | 170 + src/output/memory.rs | 350 + src/output/mod.rs | 6 + src/output/report.rs | 57 + src/repository/blob_store.rs | 835 + src/repository/current_repo_index.rs | 289 + src/repository/fetch/current_repository.rs | 234 + src/repository/fetch/http.rs | 922 + src/repository/fetch/mod.rs | 4 + src/repository/fetch/rsync.rs | 214 + src/repository/fetch/rsync_system.rs | 927 + src/repository/mod.rs | 6 + src/repository/storage.rs | 43 + src/repository/storage/config.rs | 43 + src/repository/storage/keys.rs | 130 + src/repository/storage/memory.rs | 191 + src/repository/storage/models_core.rs | 153 + src/repository/storage/models_summary.rs | 117 + src/repository/storage/pack.rs | 200 + src/repository/storage/store_lifecycle.rs | 158 + src/repository/storage/store_manifest.rs | 33 + src/repository/storage/store_repository.rs | 363 + .../storage/store_transport_rrdp.rs | 108 + src/repository/storage/tests.rs | 52 + .../storage/tests_parts/object_loading.rs | 234 + .../storage/tests_parts/repository.rs | 391 + src/repository/storage/tests_parts/rrdp.rs | 262 + src/repository/storage/verification.rs | 32 + src/repository/sync/mod.rs | 3 + src/repository/sync/repo.rs | 516 + src/repository/sync/repo/tests.rs | 2 + .../sync/repo/tests_parts/setup_and_sync.rs | 371 + src/repository/sync/rrdp.rs | 56 + src/repository/sync/rrdp/delta.rs | 272 + .../sync/rrdp/models_and_parsing.rs | 639 + src/repository/sync/rrdp/notification_sync.rs | 375 + src/repository/sync/rrdp/parse_helpers.rs | 87 + src/repository/sync/rrdp/snapshot_apply.rs | 454 + src/repository/sync/rrdp/snapshot_sync.rs | 170 + src/repository/sync/rrdp/tests.rs | 5 + .../sync/rrdp/tests_parts/delta_apply.rs | 359 + .../sync/rrdp/tests_parts/edge_cases.rs | 214 + .../sync/rrdp/tests_parts/parsing.rs | 374 + src/repository/sync/rrdp/tests_parts/sync.rs | 470 + src/repository/sync/store_projection.rs | 510 + src/runtime.rs | 43 + src/runtime/output.rs | 419 + src/runtime/post_validation.rs | 545 + src/runtime/report.rs | 32 + src/runtime/report_tasks.rs | 118 + src/runtime/run.rs | 548 + src/runtime/tests.rs | 169 + src/runtime/types.rs | 93 + src/scheduler/config.rs | 73 + src/scheduler/mod.rs | 9 + src/scheduler/object_worker.rs | 480 + src/scheduler/phase2_scheduler.rs | 332 + src/scheduler/repo_runtime.rs | 21 + src/scheduler/repo_runtime/outcome.rs | 58 + src/scheduler/repo_runtime/phase1_runtime.rs | 329 + .../repo_runtime/runtime_trait_impl.rs | 68 + src/scheduler/repo_runtime/types_and_trait.rs | 78 + src/scheduler/repo_scheduler.rs | 84 + src/scheduler/repo_scheduler/repo_state.rs | 199 + src/scheduler/repo_scheduler/tests_repo.rs | 231 + .../repo_scheduler/tests_transport.rs | 864 + .../repo_scheduler/transport_state.rs | 719 + src/scheduler/repo_worker.rs | 30 + src/scheduler/repo_worker/executors.rs | 376 + src/scheduler/repo_worker/pools.rs | 253 + src/scheduler/repo_worker/tests.rs | 598 + src/scheduler/run_coordinator.rs | 234 + src/scheduler/stats.rs | 25 + src/scheduler/types.rs | 533 + src/ta_constraints.rs | 9 + src/ta_constraints/implementation.rs | 688 + src/ta_constraints/tests.rs | 313 + src/test_support.rs | 48 + src/validation/ca_instance.rs | 132 + src/validation/ca_path.rs | 11 + src/validation/ca_path/certificate_checks.rs | 138 + src/validation/ca_path/increment.rs | 13 + src/validation/ca_path/ip_resources.rs | 274 + src/validation/ca_path/resource_resolution.rs | 440 + src/validation/ca_path/tests.rs | 913 + .../ca_path/types_and_validation.rs | 464 + src/validation/cert_path.rs | 725 + src/validation/from_tal.rs | 533 + src/validation/manifest.rs | 19 + src/validation/manifest/helpers.rs | 96 + src/validation/manifest/models_and_process.rs | 679 + src/validation/manifest/tests.rs | 606 + src/validation/mod.rs | 14 + src/validation/objects.rs | 36 + src/validation/objects/object_validation.rs | 100 + src/validation/objects/outputs.rs | 88 + src/validation/objects/parallel_processing.rs | 331 + src/validation/objects/parallel_stage.rs | 377 + src/validation/objects/resource_validation.rs | 642 + src/validation/objects/serial_processing.rs | 117 + src/validation/policy.rs | 196 + src/validation/publication_point.rs | 18 + src/validation/run.rs | 86 + src/validation/run_tree_from_tal.rs | 42 + src/validation/run_tree_from_tal/discovery.rs | 360 + src/validation/run_tree_from_tal/phase1.rs | 365 + src/validation/run_tree_from_tal/serial.rs | 344 + src/validation/tree.rs | 363 + src/validation/tree_parallel.rs | 33 + src/validation/tree_parallel/dispatch.rs | 450 + src/validation/tree_parallel/finalize.rs | 488 + src/validation/tree_parallel/phase2.rs | 445 + src/validation/tree_parallel/ready_stage.rs | 728 + src/validation/tree_parallel/state.rs | 564 + src/validation/tree_runner.rs | 68 + .../tree_runner/audit_projection.rs | 164 + .../tree_runner/child_validation.rs | 43 + src/validation/tree_runner/discovery.rs | 329 + src/validation/tree_runner/ephemeral_state.rs | 152 + src/validation/tree_runner/fresh_pipeline.rs | 324 + src/validation/tree_runner/labels.rs | 86 + .../tree_runner/publication_point_runner.rs | 223 + src/validation/tree_runner/timing.rs | 23 + src/validation/tree_runner/types.rs | 42 + src/validation/x509_name.rs | 85 + tests/daemon_e2e.rs | 9 + tests/support/README.md | 13 + tests/support/generate_repository.py | 717 + tests/support/serve_repository.py | 15 + tests/support/test_daemon.py | 158 + tests/synthetic_docker_e2e.sh | 139 + 203 files changed, 92495 insertions(+) create mode 100644 .dockerignore create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 THIRD_PARTY_NOTICES.md create mode 100644 docker/Dockerfile create mode 100644 docker/compose.all5.yaml create mode 100644 docker/compose.daemon.yaml create mode 100644 docker/compose.yaml create mode 100644 docs/command-line-reference.md create mode 100644 docs/conformance-matrix.md create mode 100644 docs/development.md create mode 100644 docs/docker.md create mode 100644 docs/getting-started.md create mode 100644 docs/third-party-licenses.txt create mode 100644 docs/trust-anchors.md create mode 100644 docs/usage.md create mode 100644 src/ccr/accumulator.rs create mode 100644 src/ccr/build.rs create mode 100644 src/ccr/compare_view.rs create mode 100644 src/ccr/decode.rs create mode 100644 src/ccr/encode.rs create mode 100644 src/ccr/export.rs create mode 100644 src/ccr/hash.rs create mode 100644 src/ccr/manifest_location.rs create mode 100644 src/ccr/mod.rs create mode 100644 src/ccr/model.rs create mode 100644 src/ccr/projection.rs create mode 100644 src/ccr/verify.rs create mode 100644 src/cli/mod.rs create mode 100644 src/cli/validate.rs create mode 100644 src/daemon/metrics.rs create mode 100644 src/daemon/mod.rs create mode 100644 src/daemon/process.rs create mode 100644 src/daemon/storage.rs create mode 100644 src/daemon/tests.rs create mode 100644 src/lib.rs create mode 100644 src/logging.rs create mode 100644 src/logging/progress.rs create mode 100644 src/main.rs create mode 100644 src/model/aspa.rs create mode 100644 src/model/common.rs create mode 100644 src/model/crl.rs create mode 100644 src/model/manifest.rs create mode 100644 src/model/mod.rs create mode 100644 src/model/oid.rs create mode 100644 src/model/projection.rs create mode 100644 src/model/rc.rs create mode 100644 src/model/rc/certificate_validation.rs create mode 100644 src/model/rc/parsed_validation.rs create mode 100644 src/model/rc/parsing.rs create mode 100644 src/model/rc/strict_name_tests.rs create mode 100644 src/model/rc/types.rs create mode 100644 src/model/roa.rs create mode 100644 src/model/router_cert.rs create mode 100644 src/model/signed_object.rs create mode 100644 src/model/signed_object/cms_reader.rs create mode 100644 src/model/signed_object/parsed_profile.rs create mode 100644 src/model/signed_object/signed_attrs.rs create mode 100644 src/model/signed_object/signed_object_impl.rs create mode 100644 src/model/signed_object/tests.rs create mode 100644 src/model/signed_object/types_errors.rs create mode 100644 src/model/ta.rs create mode 100644 src/model/tal.rs create mode 100644 src/output/analysis/mod.rs create mode 100644 src/output/analysis/timing.rs create mode 100644 src/output/audit.rs create mode 100644 src/output/audit_downloads.rs create mode 100644 src/output/memory.rs create mode 100644 src/output/mod.rs create mode 100644 src/output/report.rs create mode 100644 src/repository/blob_store.rs create mode 100644 src/repository/current_repo_index.rs create mode 100644 src/repository/fetch/current_repository.rs create mode 100644 src/repository/fetch/http.rs create mode 100644 src/repository/fetch/mod.rs create mode 100644 src/repository/fetch/rsync.rs create mode 100644 src/repository/fetch/rsync_system.rs create mode 100644 src/repository/mod.rs create mode 100644 src/repository/storage.rs create mode 100644 src/repository/storage/config.rs create mode 100644 src/repository/storage/keys.rs create mode 100644 src/repository/storage/memory.rs create mode 100644 src/repository/storage/models_core.rs create mode 100644 src/repository/storage/models_summary.rs create mode 100644 src/repository/storage/pack.rs create mode 100644 src/repository/storage/store_lifecycle.rs create mode 100644 src/repository/storage/store_manifest.rs create mode 100644 src/repository/storage/store_repository.rs create mode 100644 src/repository/storage/store_transport_rrdp.rs create mode 100644 src/repository/storage/tests.rs create mode 100644 src/repository/storage/tests_parts/object_loading.rs create mode 100644 src/repository/storage/tests_parts/repository.rs create mode 100644 src/repository/storage/tests_parts/rrdp.rs create mode 100644 src/repository/storage/verification.rs create mode 100644 src/repository/sync/mod.rs create mode 100644 src/repository/sync/repo.rs create mode 100644 src/repository/sync/repo/tests.rs create mode 100644 src/repository/sync/repo/tests_parts/setup_and_sync.rs create mode 100644 src/repository/sync/rrdp.rs create mode 100644 src/repository/sync/rrdp/delta.rs create mode 100644 src/repository/sync/rrdp/models_and_parsing.rs create mode 100644 src/repository/sync/rrdp/notification_sync.rs create mode 100644 src/repository/sync/rrdp/parse_helpers.rs create mode 100644 src/repository/sync/rrdp/snapshot_apply.rs create mode 100644 src/repository/sync/rrdp/snapshot_sync.rs create mode 100644 src/repository/sync/rrdp/tests.rs create mode 100644 src/repository/sync/rrdp/tests_parts/delta_apply.rs create mode 100644 src/repository/sync/rrdp/tests_parts/edge_cases.rs create mode 100644 src/repository/sync/rrdp/tests_parts/parsing.rs create mode 100644 src/repository/sync/rrdp/tests_parts/sync.rs create mode 100644 src/repository/sync/store_projection.rs create mode 100644 src/runtime.rs create mode 100644 src/runtime/output.rs create mode 100644 src/runtime/post_validation.rs create mode 100644 src/runtime/report.rs create mode 100644 src/runtime/report_tasks.rs create mode 100644 src/runtime/run.rs create mode 100644 src/runtime/tests.rs create mode 100644 src/runtime/types.rs create mode 100644 src/scheduler/config.rs create mode 100644 src/scheduler/mod.rs create mode 100644 src/scheduler/object_worker.rs create mode 100644 src/scheduler/phase2_scheduler.rs create mode 100644 src/scheduler/repo_runtime.rs create mode 100644 src/scheduler/repo_runtime/outcome.rs create mode 100644 src/scheduler/repo_runtime/phase1_runtime.rs create mode 100644 src/scheduler/repo_runtime/runtime_trait_impl.rs create mode 100644 src/scheduler/repo_runtime/types_and_trait.rs create mode 100644 src/scheduler/repo_scheduler.rs create mode 100644 src/scheduler/repo_scheduler/repo_state.rs create mode 100644 src/scheduler/repo_scheduler/tests_repo.rs create mode 100644 src/scheduler/repo_scheduler/tests_transport.rs create mode 100644 src/scheduler/repo_scheduler/transport_state.rs create mode 100644 src/scheduler/repo_worker.rs create mode 100644 src/scheduler/repo_worker/executors.rs create mode 100644 src/scheduler/repo_worker/pools.rs create mode 100644 src/scheduler/repo_worker/tests.rs create mode 100644 src/scheduler/run_coordinator.rs create mode 100644 src/scheduler/stats.rs create mode 100644 src/scheduler/types.rs create mode 100644 src/ta_constraints.rs create mode 100644 src/ta_constraints/implementation.rs create mode 100644 src/ta_constraints/tests.rs create mode 100644 src/test_support.rs create mode 100644 src/validation/ca_instance.rs create mode 100644 src/validation/ca_path.rs create mode 100644 src/validation/ca_path/certificate_checks.rs create mode 100644 src/validation/ca_path/increment.rs create mode 100644 src/validation/ca_path/ip_resources.rs create mode 100644 src/validation/ca_path/resource_resolution.rs create mode 100644 src/validation/ca_path/tests.rs create mode 100644 src/validation/ca_path/types_and_validation.rs create mode 100644 src/validation/cert_path.rs create mode 100644 src/validation/from_tal.rs create mode 100644 src/validation/manifest.rs create mode 100644 src/validation/manifest/helpers.rs create mode 100644 src/validation/manifest/models_and_process.rs create mode 100644 src/validation/manifest/tests.rs create mode 100644 src/validation/mod.rs create mode 100644 src/validation/objects.rs create mode 100644 src/validation/objects/object_validation.rs create mode 100644 src/validation/objects/outputs.rs create mode 100644 src/validation/objects/parallel_processing.rs create mode 100644 src/validation/objects/parallel_stage.rs create mode 100644 src/validation/objects/resource_validation.rs create mode 100644 src/validation/objects/serial_processing.rs create mode 100644 src/validation/policy.rs create mode 100644 src/validation/publication_point.rs create mode 100644 src/validation/run.rs create mode 100644 src/validation/run_tree_from_tal.rs create mode 100644 src/validation/run_tree_from_tal/discovery.rs create mode 100644 src/validation/run_tree_from_tal/phase1.rs create mode 100644 src/validation/run_tree_from_tal/serial.rs create mode 100644 src/validation/tree.rs create mode 100644 src/validation/tree_parallel.rs create mode 100644 src/validation/tree_parallel/dispatch.rs create mode 100644 src/validation/tree_parallel/finalize.rs create mode 100644 src/validation/tree_parallel/phase2.rs create mode 100644 src/validation/tree_parallel/ready_stage.rs create mode 100644 src/validation/tree_parallel/state.rs create mode 100644 src/validation/tree_runner.rs create mode 100644 src/validation/tree_runner/audit_projection.rs create mode 100644 src/validation/tree_runner/child_validation.rs create mode 100644 src/validation/tree_runner/discovery.rs create mode 100644 src/validation/tree_runner/ephemeral_state.rs create mode 100644 src/validation/tree_runner/fresh_pipeline.rs create mode 100644 src/validation/tree_runner/labels.rs create mode 100644 src/validation/tree_runner/publication_point_runner.rs create mode 100644 src/validation/tree_runner/timing.rs create mode 100644 src/validation/tree_runner/types.rs create mode 100644 src/validation/x509_name.rs create mode 100644 tests/daemon_e2e.rs create mode 100644 tests/support/README.md create mode 100644 tests/support/generate_repository.py create mode 100644 tests/support/serve_repository.py create mode 100644 tests/support/test_daemon.py create mode 100755 tests/synthetic_docker_e2e.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1fce1cd --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +.git +.github +target +out +state +tests +docs +!docs/third-party-licenses.txt +docker +input +output +data +*.log diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0a86e2a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: ci + +on: + push: + pull_request: + +permissions: + contents: read + +jobs: + rust: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: dtolnay/rust-toolchain@6d653ac52d5f748757700b3b0ec16abf11083d92 + with: + toolchain: 1.92.0 + components: rustfmt, clippy + - run: sudo apt-get update && sudo apt-get install -y --no-install-recommends build-essential clang libclang-dev pkg-config python3-cryptography openssl + - run: cargo fmt --all --check + - run: cargo check --locked --no-default-features + - run: cargo test --locked + - run: cargo clippy --locked --all-targets -- -D warnings + - run: cargo build --locked --release + docker: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - run: sudo apt-get update && sudo apt-get install -y --no-install-recommends python3-cryptography openssl + - run: docker build -f docker/Dockerfile -t panda-rpki:ci . + - run: docker run --rm panda-rpki:ci --help + - run: bash tests/synthetic_docker_e2e.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..562d019 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +/target/ +/out/ +/state/ +/input/ +/output/ +/data/ +__pycache__/ +*.pyc +*.log +.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8c846b8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +## 0.1.0 — Unreleased + +- RPKI validation with multiple TAL/TA inputs and resource constraints. +- RRDP snapshot/delta synchronization and same-origin enforcement. +- Bounded transport and object worker pools. +- CSV, CCR, JSON summaries and level-controlled text/JSON logs. +- Foreground daemon with intervals, timeouts, restart and retention. +- Docker and Compose deployment, including all-five-anchor configuration. + +The CLI accepts HTTPS RRDP. Operators supply TALs and matching certificates. +No RTR server is included. See the standards matrix and CLI reference for scope. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5ffb682 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,14 @@ +# Contributing + +Describe reproducible problems and proposed changes in an issue or pull request. +Include the version, command, expected and actual results. Remove credentials +and sensitive paths from shared logs. + +Run [development checks](docs/development.md), keep changes focused, use +`cargo fmt`, and add regression tests for changed behavior. Explain relevant +RFC/WG requirements and update documentation when interfaces change. + +Documentation and code comments use English. Do not commit generated repository +objects, keys, state, logs or build outputs. Keep third-party attribution intact. +Submit only code you are entitled to contribute under the [project license](LICENSE) +and identify externally sourced code and its license in the pull request. diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c427e5b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2133 @@ +# 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 = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "asn1-rs" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "async-compression" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "515a1f282e33d55983c499d7e9e87082e81cbc32974825bf9032f928392d5844" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex 1.3.0", + "syn 2.0.119", +] + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[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 = "brotli" +version = "8.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "cc" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex 2.0.1", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[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.1", + "rand_core", +] + +[[package]] +name = "clang-sys" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "compression-codecs" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fe67f2944eef52fc7b106b8c9450d243a88701a0c065f7f57235e76abaed7df" +dependencies = [ + "brotli", + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e8ccc4ea9f6acc32d102c0f6d471d11d913ad15f20c04de743374861fa1d414" + +[[package]] +name = "cookie-factory" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" +dependencies = [ + "futures", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" +dependencies = [ + "cfg-if", +] + +[[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 = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "cookie-factory", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" + +[[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 = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "flate2" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[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 = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "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 = "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.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "rand_core", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "half" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +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 = "hyper" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "librocksdb-sys" +version = "0.16.0+8.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce3d60bc059831dc1c83903fb45c103f75db65c5a7bf22272764d9cc683e348c" +dependencies = [ + "bindgen", + "bzip2-sys", + "cc", + "glob", + "libc", + "libz-sys", + "lz4-sys", +] + +[[package]] +name = "libz-sys" +version = "1.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lz4-sys" +version = "1.11.1+lz4-1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "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 = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "panda-rpki" +version = "0.1.0" +dependencies = [ + "asn1-rs", + "base64", + "der-parser", + "hex", + "libc", + "quick-xml", + "reqwest", + "ring", + "rocksdb", + "roxmltree", + "serde", + "serde_cbor", + "serde_json", + "sha2", + "tempfile", + "thiserror", + "time", + "toml", + "url", + "uuid", + "x509-parser", +] + +[[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 = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[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 = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.3", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83" +dependencies = [ + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand", + "rand_pcg", + "ring", + "rustc-hash 2.1.3", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.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 = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + +[[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", + "windows-sys 0.52.0", +] + +[[package]] +name = "rocksdb" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd13e55d6d7b8cd0ea569161127567cd587676c99f4472f779a0279aa60a7a7" +dependencies = [ + "libc", + "librocksdb-sys", +] + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[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_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.4", +] + +[[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_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[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 = "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", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +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.119", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf0ded5c4e56918d8f8a339e1bb67d038d3bc6d144ac407904015ba2e4cde9b" +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.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[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.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[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 = [ + "pin-project-lite", + "tracing-core", +] + +[[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 = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[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", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[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 = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +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-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[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_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "ring", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "zlib-rs" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3c18d11 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "panda-rpki" +version = "0.1.0" +edition = "2024" +rust-version = "1.92" +description = "A small, strict RFC RPKI validation core" +license = "BSD-3-Clause" +publish = false + +[dependencies] +libc = "0.2" +asn1-rs = "0.7.1" +der-parser = { version = "10.0.0", features = ["serialize"] } +hex = "0.4.3" +base64 = "0.22.1" +sha2 = "0.10.8" +thiserror = "2.0.18" +time = "0.3.45" +ring = "0.17.14" +x509-parser = { version = "0.18.0", features = ["verify"] } +url = "2.5.8" +serde = { version = "1.0.218", features = ["derive"] } +serde_json = { version = "1.0.140", features = ["raw_value"] } +toml = "0.8.20" +rocksdb = { version = "0.22.0", default-features = false, features = ["lz4"] } +serde_cbor = "0.11.2" +roxmltree = "0.20.0" +quick-xml = "0.37.2" +uuid = { version = "1.7.0", features = ["v4"] } +reqwest = { version = "0.12.12", default-features = false, features = ["blocking", "rustls-tls", "gzip", "brotli", "deflate"] } +tempfile = "3.16.0" + +[lints.rust] +unsafe_code = "warn" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..859a1ba --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2026, Panda RPKI contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0dedc56 --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# Panda RPKI + +Panda RPKI is an RPKI relying party written in Rust. It synchronizes repositories +over RRDP, validates signed objects, and exports routing data as CSV and Canonical +Cache Representation (CCR). Run a single cycle or use the daemon for continuous operation. + +## Quick start + +Requirements: Linux, Docker, and a TAL with its matching DER trust-anchor +certificate. Follow [input preparation](docs/getting-started.md#prepare-trust-anchors) +to obtain these from your chosen RIR. From the project root, after placing them +in `input/anchor.tal` and `input/anchor.cer`: + +```bash +docker build -f docker/Dockerfile -t panda-rpki:v0.1.0 . +mkdir -p state output +docker run --rm --read-only --tmpfs /tmp \ + --user "$(id -u):$(id -g)" \ + -v "$PWD/input:/input:ro" \ + -v "$PWD/state:/state" \ + -v "$PWD/output:/output" \ + panda-rpki:v0.1.0 validate \ + --tal /input/anchor.tal --ta /input/anchor.cer \ + --rrdp-state-dir /state --out /output \ + --ccr-out /output/result.ccr +``` + +Inspect `output/summary.json` and `output/vrps.csv`. Logs go to stderr. +Reuse the state directory to allow delta updates on subsequent runs. +For continuous operation see [Usage](docs/usage.md#continuous-operation). + +## Features + +- Multiple TAL/TA pairs and optional per-anchor resource constraints. +- RRDP snapshot, delta, replace/withdraw and protocol fallback. +- HTTPS same-origin checks for RRDP references and redirects. +- Independent bounded repository and object worker pools. +- CA, CRL, manifest, ROA, ASPA and BGPsec router-certificate processing. +- CSV, CCR, JSON summaries and configurable text or JSON logs. +- Daemon intervals, persistent state, run retention, timeouts and graceful stop. + +Version 0.1.0 accepts HTTPS RRDP through its CLI. Operators supply TALs and +matching TA certificates. No RTR server is included. See the +[standards matrix](docs/conformance-matrix.md) for profiles and limitations. + +## Documentation + +- [Getting started](docs/getting-started.md): dependencies, inputs and first run. +- [Five-RIR trust anchors](docs/trust-anchors.md): official TAL/TA downloads and checks. +- [Usage](docs/usage.md): multiple anchors, delta, daemon and troubleshooting. +- [Command-line reference](docs/command-line-reference.md): options and defaults. +- [Docker and Compose](docs/docker.md): single-anchor, all-five and daemon deployments. +- [Development and testing](docs/development.md). +- [Contributing](CONTRIBUTING.md) and [changelog](CHANGELOG.md). + +## Build from source + +Install Rust 1.92 or newer and the [native dependencies](docs/getting-started.md#build-from-source): + +```bash +cargo build --locked --release +target/release/panda-rpki --help +``` + +## License + +Panda RPKI uses the [BSD-3-Clause license](LICENSE). +See [third-party notices](THIRD_PARTY_NOTICES.md) for dependency attribution. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..fe7f117 --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,239 @@ +# Third-party notices + +Panda RPKI is distributed under BSD-3-Clause; dependencies retain their own +licenses. The table below records the locked Cargo dependency graph, including +build-time and platform-specific packages that may not be linked on Linux. +Upstream license expressions are reproduced as declared by each package. + +The accompanying [license texts](docs/third-party-licenses.txt) retain notices +from package archives, including bundled native code. These files must accompany +binary distributions. Container OS package notices remain under +`/usr/share/doc/` and `/usr/share/common-licenses/` in the Debian image. + +Python cryptography and OpenSSL are external test tools, not bundled source. +Consult their installed license notices when distributing a test environment. +No production trust-anchor or repository data is included in this source tree. + +| Package | Locked version | Declared license | +| --- | --- | --- | +| adler2 | 2.0.1 | 0BSD OR MIT OR Apache-2.0 | +| aho-corasick | 1.1.5 | Unlicense OR MIT | +| alloc-no-stdlib | 2.0.4 | BSD-3-Clause | +| alloc-stdlib | 0.2.4 | BSD-3-Clause | +| asn1-rs | 0.7.2 | MIT OR Apache-2.0 | +| asn1-rs-derive | 0.6.0 | MIT OR Apache-2.0 | +| asn1-rs-impl | 0.2.0 | MIT/Apache-2.0 | +| async-compression | 0.4.44 | MIT OR Apache-2.0 | +| atomic-waker | 1.1.2 | Apache-2.0 OR MIT | +| autocfg | 1.5.1 | Apache-2.0 OR MIT | +| base64 | 0.22.1 | MIT OR Apache-2.0 | +| bindgen | 0.69.5 | BSD-3-Clause | +| bitflags | 2.13.1 | MIT OR Apache-2.0 | +| block-buffer | 0.10.4 | MIT OR Apache-2.0 | +| brotli | 8.0.4 | BSD-3-Clause AND MIT | +| brotli-decompressor | 5.0.3 | BSD-3-Clause/MIT | +| bumpalo | 3.20.3 | MIT OR Apache-2.0 | +| bytes | 1.12.1 | MIT | +| bzip2-sys | 0.1.13+1.0.8 | MIT/Apache-2.0 | +| cc | 1.4.4 | MIT OR Apache-2.0 | +| cexpr | 0.6.0 | Apache-2.0/MIT | +| cfg-if | 1.0.4 | MIT OR Apache-2.0 | +| cfg_aliases | 0.2.2 | MIT | +| chacha20 | 0.10.2 | MIT OR Apache-2.0 | +| clang-sys | 1.9.1 | Apache-2.0 | +| compression-codecs | 0.4.39 | MIT OR Apache-2.0 | +| compression-core | 0.4.33 | MIT OR Apache-2.0 | +| cookie-factory | 0.3.3 | MIT | +| cpufeatures | 0.2.17 | MIT OR Apache-2.0 | +| cpufeatures | 0.3.1 | MIT OR Apache-2.0 | +| crc32fast | 1.5.1 | MIT OR Apache-2.0 | +| crypto-common | 0.1.7 | MIT OR Apache-2.0 | +| data-encoding | 2.11.1 | MIT | +| der-parser | 10.0.0 | MIT OR Apache-2.0 | +| deranged | 0.5.8 | MIT OR Apache-2.0 | +| digest | 0.10.7 | MIT OR Apache-2.0 | +| displaydoc | 0.2.7 | MIT OR Apache-2.0 | +| either | 1.18.0 | MIT OR Apache-2.0 | +| equivalent | 1.0.2 | Apache-2.0 OR MIT | +| errno | 0.3.14 | MIT OR Apache-2.0 | +| fastrand | 2.5.0 | Apache-2.0 OR MIT | +| find-msvc-tools | 0.1.11 | MIT OR Apache-2.0 | +| flate2 | 1.1.10 | MIT OR Apache-2.0 | +| form_urlencoded | 1.2.2 | MIT OR Apache-2.0 | +| futures | 0.3.34 | MIT OR Apache-2.0 | +| futures-channel | 0.3.34 | MIT OR Apache-2.0 | +| futures-core | 0.3.34 | MIT OR Apache-2.0 | +| futures-executor | 0.3.34 | MIT OR Apache-2.0 | +| futures-io | 0.3.34 | MIT OR Apache-2.0 | +| futures-macro | 0.3.34 | MIT OR Apache-2.0 | +| futures-sink | 0.3.34 | MIT OR Apache-2.0 | +| futures-task | 0.3.34 | MIT OR Apache-2.0 | +| futures-util | 0.3.34 | MIT OR Apache-2.0 | +| generic-array | 0.14.7 | MIT | +| getrandom | 0.2.17 | MIT OR Apache-2.0 | +| getrandom | 0.4.3 | MIT OR Apache-2.0 | +| glob | 0.3.4 | MIT OR Apache-2.0 | +| half | 1.8.3 | MIT OR Apache-2.0 | +| hashbrown | 0.17.1 | MIT OR Apache-2.0 | +| hex | 0.4.3 | MIT OR Apache-2.0 | +| http | 1.5.0 | MIT OR Apache-2.0 | +| http-body | 1.1.0 | MIT | +| http-body-util | 0.1.5 | MIT | +| httparse | 1.10.1 | MIT OR Apache-2.0 | +| hyper | 1.11.1 | MIT | +| hyper-rustls | 0.27.9 | Apache-2.0 OR ISC OR MIT | +| hyper-util | 0.1.20 | MIT | +| icu_collections | 2.3.0 | Unicode-3.0 | +| icu_locale_core | 2.3.0 | Unicode-3.0 | +| icu_normalizer | 2.3.0 | Unicode-3.0 | +| icu_normalizer_data | 2.3.0 | Unicode-3.0 | +| icu_properties | 2.3.0 | Unicode-3.0 | +| icu_properties_data | 2.3.0 | Unicode-3.0 | +| icu_provider | 2.3.1 | Unicode-3.0 | +| idna | 1.1.0 | MIT OR Apache-2.0 | +| idna_adapter | 1.2.2 | Apache-2.0 OR MIT | +| indexmap | 2.14.2 | Apache-2.0 OR MIT | +| ipnet | 2.12.1 | MIT OR Apache-2.0 | +| itertools | 0.12.1 | MIT OR Apache-2.0 | +| itoa | 1.0.18 | MIT OR Apache-2.0 | +| jobserver | 0.1.35 | MIT OR Apache-2.0 | +| js-sys | 0.3.104 | MIT OR Apache-2.0 | +| lazy_static | 1.5.0 | MIT OR Apache-2.0 | +| lazycell | 1.3.0 | MIT/Apache-2.0 | +| libc | 0.2.189 | MIT OR Apache-2.0 | +| libloading | 0.8.9 | ISC | +| librocksdb-sys | 0.16.0+8.10.0 | MIT/Apache-2.0/BSD-3-Clause | +| libz-sys | 1.1.29 | MIT OR Apache-2.0 | +| linux-raw-sys | 0.12.1 | Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT | +| litemap | 0.8.3 | Unicode-3.0 | +| log | 0.4.34 | MIT OR Apache-2.0 | +| lru-slab | 0.1.2 | MIT OR Apache-2.0 OR Zlib | +| lz4-sys | 1.11.1+lz4-1.10.0 | MIT | +| memchr | 2.8.3 | Unlicense OR MIT | +| minimal-lexical | 0.2.1 | MIT/Apache-2.0 | +| miniz_oxide | 0.9.1 | MIT OR Zlib OR Apache-2.0 | +| mio | 1.2.3 | MIT | +| nom | 7.1.3 | MIT | +| num-bigint | 0.4.8 | MIT OR Apache-2.0 | +| num-conv | 0.2.2 | MIT OR Apache-2.0 | +| num-integer | 0.1.47 | MIT OR Apache-2.0 | +| num-traits | 0.2.19 | MIT OR Apache-2.0 | +| oid-registry | 0.8.1 | MIT OR Apache-2.0 | +| once_cell | 1.21.4 | MIT OR Apache-2.0 | +| percent-encoding | 2.3.2 | MIT OR Apache-2.0 | +| pin-project-lite | 0.2.17 | Apache-2.0 OR MIT | +| pkg-config | 0.3.34 | MIT OR Apache-2.0 | +| potential_utf | 0.1.6 | Unicode-3.0 | +| powerfmt | 0.2.0 | MIT OR Apache-2.0 | +| proc-macro2 | 1.0.107 | MIT OR Apache-2.0 | +| quick-xml | 0.37.5 | MIT | +| quinn | 0.11.11 | MIT OR Apache-2.0 | +| quinn-proto | 0.11.17 | MIT OR Apache-2.0 | +| quinn-udp | 0.5.15 | MIT OR Apache-2.0 | +| quote | 1.0.47 | MIT OR Apache-2.0 | +| r-efi | 6.0.0 | MIT OR Apache-2.0 OR LGPL-2.1-or-later | +| rand | 0.10.2 | MIT OR Apache-2.0 | +| rand_core | 0.10.1 | MIT OR Apache-2.0 | +| rand_pcg | 0.10.2 | MIT OR Apache-2.0 | +| regex | 1.13.1 | MIT OR Apache-2.0 | +| regex-automata | 0.4.18 | MIT OR Apache-2.0 | +| regex-syntax | 0.8.11 | MIT OR Apache-2.0 | +| reqwest | 0.12.28 | MIT OR Apache-2.0 | +| ring | 0.17.14 | Apache-2.0 AND ISC | +| rocksdb | 0.22.0 | Apache-2.0 | +| roxmltree | 0.20.0 | MIT OR Apache-2.0 | +| rustc-hash | 1.1.0 | Apache-2.0/MIT | +| rustc-hash | 2.1.3 | Apache-2.0 OR MIT | +| rusticata-macros | 4.1.0 | MIT/Apache-2.0 | +| rustix | 1.1.4 | Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT | +| rustls | 0.23.43 | Apache-2.0 OR ISC OR MIT | +| rustls-pki-types | 1.15.1 | MIT OR Apache-2.0 | +| rustls-webpki | 0.103.15 | ISC | +| rustversion | 1.0.23 | MIT OR Apache-2.0 | +| ryu | 1.0.23 | Apache-2.0 OR BSL-1.0 | +| serde | 1.0.229 | MIT OR Apache-2.0 | +| serde_cbor | 0.11.2 | MIT/Apache-2.0 | +| serde_core | 1.0.229 | MIT OR Apache-2.0 | +| serde_derive | 1.0.229 | MIT OR Apache-2.0 | +| serde_json | 1.0.151 | MIT OR Apache-2.0 | +| serde_spanned | 0.6.9 | MIT OR Apache-2.0 | +| serde_urlencoded | 0.7.1 | MIT/Apache-2.0 | +| sha2 | 0.10.9 | MIT OR Apache-2.0 | +| shlex | 1.3.0 | MIT OR Apache-2.0 | +| shlex | 2.0.1 | MIT OR Apache-2.0 | +| simd-adler32 | 0.3.10 | MIT | +| slab | 0.4.12 | MIT | +| smallvec | 1.16.0 | MIT OR Apache-2.0 | +| socket2 | 0.6.5 | MIT OR Apache-2.0 | +| stable_deref_trait | 1.2.1 | MIT OR Apache-2.0 | +| subtle | 2.6.1 | BSD-3-Clause | +| syn | 2.0.119 | MIT OR Apache-2.0 | +| syn | 3.0.4 | MIT OR Apache-2.0 | +| sync_wrapper | 1.0.2 | Apache-2.0 | +| synstructure | 0.13.2 | MIT | +| tempfile | 3.27.0 | MIT OR Apache-2.0 | +| thiserror | 2.0.20 | MIT OR Apache-2.0 | +| thiserror-impl | 2.0.20 | MIT OR Apache-2.0 | +| time | 0.3.55 | MIT OR Apache-2.0 | +| time-core | 0.1.9 | MIT OR Apache-2.0 | +| time-macros | 0.2.32 | MIT OR Apache-2.0 | +| tinystr | 0.8.4 | Unicode-3.0 | +| tinyvec | 1.13.2 | Zlib OR Apache-2.0 OR MIT | +| tinyvec_macros | 0.1.1 | MIT OR Apache-2.0 OR Zlib | +| tokio | 1.53.1 | MIT | +| tokio-rustls | 0.26.4 | MIT OR Apache-2.0 | +| tokio-util | 0.7.19 | MIT | +| toml | 0.8.23 | MIT OR Apache-2.0 | +| toml_datetime | 0.6.11 | MIT OR Apache-2.0 | +| toml_edit | 0.22.27 | MIT OR Apache-2.0 | +| toml_write | 0.1.2 | MIT OR Apache-2.0 | +| tower | 0.5.3 | MIT | +| tower-http | 0.6.11 | MIT | +| tower-layer | 0.3.3 | MIT | +| tower-service | 0.3.3 | MIT | +| tracing | 0.1.44 | MIT | +| tracing-core | 0.1.36 | MIT | +| try-lock | 0.2.5 | MIT | +| typenum | 1.20.1 | MIT OR Apache-2.0 | +| unicode-ident | 1.0.24 | (MIT OR Apache-2.0) AND Unicode-3.0 | +| untrusted | 0.9.0 | ISC | +| url | 2.5.8 | MIT OR Apache-2.0 | +| utf8_iter | 1.0.4 | Apache-2.0 OR MIT | +| uuid | 1.26.0 | Apache-2.0 OR MIT | +| vcpkg | 0.2.15 | MIT/Apache-2.0 | +| version_check | 0.9.5 | MIT/Apache-2.0 | +| want | 0.3.1 | MIT | +| wasi | 0.11.1+wasi-snapshot-preview1 | Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT | +| wasm-bindgen | 0.2.127 | MIT OR Apache-2.0 | +| wasm-bindgen-futures | 0.4.77 | MIT OR Apache-2.0 | +| wasm-bindgen-macro | 0.2.127 | MIT OR Apache-2.0 | +| wasm-bindgen-macro-support | 0.2.127 | MIT OR Apache-2.0 | +| wasm-bindgen-shared | 0.2.127 | MIT OR Apache-2.0 | +| web-sys | 0.3.104 | MIT OR Apache-2.0 | +| web-time | 1.1.0 | MIT OR Apache-2.0 | +| webpki-roots | 1.0.9 | CDLA-Permissive-2.0 | +| windows-link | 0.2.1 | MIT OR Apache-2.0 | +| windows-sys | 0.52.0 | MIT OR Apache-2.0 | +| windows-sys | 0.61.2 | MIT OR Apache-2.0 | +| windows-targets | 0.52.6 | MIT OR Apache-2.0 | +| windows_aarch64_gnullvm | 0.52.6 | MIT OR Apache-2.0 | +| windows_aarch64_msvc | 0.52.6 | MIT OR Apache-2.0 | +| windows_i686_gnu | 0.52.6 | MIT OR Apache-2.0 | +| windows_i686_gnullvm | 0.52.6 | MIT OR Apache-2.0 | +| windows_i686_msvc | 0.52.6 | MIT OR Apache-2.0 | +| windows_x86_64_gnu | 0.52.6 | MIT OR Apache-2.0 | +| windows_x86_64_gnullvm | 0.52.6 | MIT OR Apache-2.0 | +| windows_x86_64_msvc | 0.52.6 | MIT OR Apache-2.0 | +| winnow | 0.7.15 | MIT | +| writeable | 0.6.4 | Unicode-3.0 | +| x509-parser | 0.18.1 | MIT OR Apache-2.0 | +| yoke | 0.8.3 | Unicode-3.0 | +| yoke-derive | 0.8.2 | Unicode-3.0 | +| zerofrom | 0.1.8 | Unicode-3.0 | +| zerofrom-derive | 0.1.7 | Unicode-3.0 | +| zeroize | 1.9.0 | Apache-2.0 OR MIT | +| zerotrie | 0.2.5 | Unicode-3.0 | +| zerovec | 0.11.8 | Unicode-3.0 | +| zerovec-derive | 0.11.6 | Unicode-3.0 | +| zlib-rs | 0.6.7 | Zlib | +| zmij | 1.0.23 | MIT | diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..a2327ce --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,22 @@ +FROM rust:1.92-bookworm AS build +WORKDIR /src +RUN apt-get update \ + && apt-get install -y --no-install-recommends clang libclang-dev \ + && rm -rf /var/lib/apt/lists/* +COPY Cargo.toml Cargo.lock ./ +RUN mkdir src && printf 'fn main() {}\n' > src/main.rs && cargo build --locked --release +COPY src ./src +RUN touch src/main.rs src/lib.rs && cargo build --locked --release + +FROM debian:bookworm-slim +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates rsync time \ + && rm -rf /var/lib/apt/lists/* \ + && useradd --system --create-home --home-dir /var/lib/panda-rpki panda +COPY --from=build /src/target/release/panda-rpki /usr/local/bin/panda-rpki +COPY LICENSE /usr/share/licenses/panda-rpki/LICENSE +COPY THIRD_PARTY_NOTICES.md /usr/share/licenses/panda-rpki/THIRD_PARTY_NOTICES.md +COPY docs/third-party-licenses.txt /usr/share/licenses/panda-rpki/docs/third-party-licenses.txt +USER panda +ENTRYPOINT ["/usr/local/bin/panda-rpki"] +CMD ["--help"] diff --git a/docker/compose.all5.yaml b/docker/compose.all5.yaml new file mode 100644 index 0000000..0764f0b --- /dev/null +++ b/docker/compose.all5.yaml @@ -0,0 +1,62 @@ +services: + validator: + build: + context: .. + dockerfile: docker/Dockerfile + image: panda-rpki:v0.1.0 + user: "${PUID:-1000}:${PGID:-1000}" + read_only: true + tmpfs: + - /tmp + volumes: + - ${INPUT_DIR:?set INPUT_DIR to the TAL/TA directory}:/input:ro + - ${OUTPUT_DIR:?set OUTPUT_DIR to a writable output directory}:/output + - ${STATE_DIR:?set STATE_DIR to a writable state directory}:/state + command: + - validate + - --tal + - /input/${AFRINIC_TAL_FILE:?set AFRINIC_TAL_FILE} + - --ta + - /input/${AFRINIC_TA_FILE:?set AFRINIC_TA_FILE} + - --tal-id + - afrinic + - --tal + - /input/${APNIC_TAL_FILE:?set APNIC_TAL_FILE} + - --ta + - /input/${APNIC_TA_FILE:?set APNIC_TA_FILE} + - --tal-id + - apnic + - --tal + - /input/${ARIN_TAL_FILE:?set ARIN_TAL_FILE} + - --ta + - /input/${ARIN_TA_FILE:?set ARIN_TA_FILE} + - --tal-id + - arin + - --tal + - /input/${LACNIC_TAL_FILE:?set LACNIC_TAL_FILE} + - --ta + - /input/${LACNIC_TA_FILE:?set LACNIC_TA_FILE} + - --tal-id + - lacnic + - --tal + - /input/${RIPE_TAL_FILE:?set RIPE_TAL_FILE} + - --ta + - /input/${RIPE_TA_FILE:?set RIPE_TA_FILE} + - --tal-id + - ripe + - --parallel-phase2-object-workers + - "${PANDA_RPKI_WORKERS:-8}" + - --parallel-max-repo-sync-workers-global + - "${PANDA_RPKI_REPO_WORKERS:-8}" + - --parallel-phase2-worker-queue-capacity + - "${PANDA_RPKI_WORKER_QUEUE_CAPACITY:-256}" + - --rrdp-state-dir + - /state + - --rrdp-sync-mode + - "${PANDA_RPKI_RRDP_SYNC_MODE:-auto}" + - --http-timeout-secs + - "${PANDA_RPKI_HTTP_TIMEOUT_SECS:-600}" + - --ccr-out + - /output/all5.ccr + - --out + - /output diff --git a/docker/compose.daemon.yaml b/docker/compose.daemon.yaml new file mode 100644 index 0000000..0c122de --- /dev/null +++ b/docker/compose.daemon.yaml @@ -0,0 +1,40 @@ +services: + validator: + build: + context: .. + dockerfile: docker/Dockerfile + image: panda-rpki:v0.1.0 + user: "${PUID:-1000}:${PGID:-1000}" + read_only: true + init: true + restart: unless-stopped + stop_grace_period: 40s + tmpfs: + - /tmp + volumes: + - ${INPUT_DIR:?set INPUT_DIR to the TAL/TA directory}:/input:ro + - ${DAEMON_DIR:?set DAEMON_DIR to a writable dedicated directory}:/data + environment: + PANDA_RPKI_LOG_LEVEL: "${PANDA_RPKI_LOG_LEVEL:-info}" + PANDA_RPKI_LOG_FORMAT: "${PANDA_RPKI_LOG_FORMAT:-json}" + command: + - daemon + - --state-root + - /data + - --interval-secs + - "${PANDA_RPKI_INTERVAL_SECS:-60}" + - --retain-runs + - "${PANDA_RPKI_RETAIN_RUNS:-10}" + - --run-timeout-secs + - "${PANDA_RPKI_RUN_TIMEOUT_SECS:-0}" + - --shutdown-grace-secs + - "30" + - -- + - --tal + - /input/${TAL_FILE:?set TAL_FILE} + - --ta + - /input/${TA_FILE:?set TA_FILE} + - --parallel-max-repo-sync-workers-global + - "${PANDA_RPKI_REPO_WORKERS:-8}" + - --parallel-phase2-object-workers + - "${PANDA_RPKI_WORKERS:-8}" diff --git a/docker/compose.yaml b/docker/compose.yaml new file mode 100644 index 0000000..21725cd --- /dev/null +++ b/docker/compose.yaml @@ -0,0 +1,28 @@ +services: + validator: + build: + context: .. + dockerfile: docker/Dockerfile + image: panda-rpki:v0.1.0 + user: "${PUID:-1000}:${PGID:-1000}" + read_only: true + tmpfs: + - /tmp + volumes: + - ${INPUT_DIR:?set INPUT_DIR to the TAL/TA directory}:/input:ro + - ${OUTPUT_DIR:?set OUTPUT_DIR to a writable output directory}:/output + - ${STATE_DIR:?set STATE_DIR to a writable state directory}:/state + command: + - validate + - --tal + - /input/${TAL_FILE:?set TAL_FILE} + - --ta + - /input/${TA_FILE:?set TA_FILE} + - --rrdp-state-dir + - /state + - --rrdp-sync-mode + - "${PANDA_RPKI_RRDP_SYNC_MODE:-auto}" + - --ccr-out + - /output/result.ccr + - --out + - /output diff --git a/docs/command-line-reference.md b/docs/command-line-reference.md new file mode 100644 index 0000000..36469ca --- /dev/null +++ b/docs/command-line-reference.md @@ -0,0 +1,139 @@ +# Command-line reference + +This reference covers v0.1.0. `panda-rpki --help` displays version and usage; +there is no separate `--version` option. + +## Syntax + +```text +panda-rpki validate --tal FILE --ta FILE --out DIRECTORY [OPTIONS] +panda-rpki daemon --state-root DIRECTORY [DAEMON OPTIONS] -- [VALIDATION OPTIONS] +``` + +Use `--name value`, not `--name=value`. Only help has a short alias (`-h`). +Values are case-sensitive; options cannot repeat unless listed as repeatable. +Paths are relative to the process working directory. No configuration-file +option is provided. No arguments or a help request exits successfully. + +## Inputs and outputs + +| Option | Default / requirement | Repeatable | Meaning | +| --- | --- | --- | --- | +| `--tal ` | Required | Yes | Local TAL file. | +| `--ta ` | Required | Yes | Matching DER trust-anchor certificate. | +| `--tal-id ` | TAL filename stem | Yes | Anchor identifier; if supplied, provide one for every TAL. | +| `--ta-constraints =` | Adjacent `.constraints` if present | Yes | Override constraints for a known TAL ID; each ID may occur once. | +| `--out ` | Required | No | Output directory, created if absent. Existing output files may be overwritten. | +| `--ccr-out ` | Disabled | No | CCR DER path, relative to the working directory, not `--out`. | + +TAL and TA lists are paired by occurrence order and must have equal lengths. +Use distinct TAL IDs. For `input/anchor.tal`, automatic discovery checks +`input/anchor.constraints`. If absent with no explicit path, no additional +constraints are loaded for that anchor. + +## State and network + +| Option | Default | Meaning | +| --- | --- | --- | +| `--rrdp-state-dir ` | `/.state` | Dedicated persistent root; database is stored in `repository-db/`. | +| `--rrdp-sync-mode ` | `auto` | Starting state requirements described below. | +| `--http-timeout-secs ` | `300` | HTTP timeout seconds; nonnegative with runtime minimum 1. Connection, retry and large-object transport policies also apply. Not a whole-run deadline. | +| `--http-root-cert ` | None | Repeatable extra HTTPS roots; system roots and hostname verification remain enabled. | +| `--max-ca-depth ` | `64` | Nonnegative CA traversal depth limit. | + +| Mode | Starting state | Behavior | +| --- | --- | --- | +| `auto` | Empty or populated database | Select snapshot, delta or noop per repository. | +| `snapshot` | Missing or empty database | Start fresh; reject a populated database. | +| `delta` | Existing populated database | Resume state, allowing protocol snapshot fallback and new-repository snapshots. | + +Unrecognized state layouts and unsupported database schemas are rejected. +Use a new dedicated directory for an incompatible format. State includes +repository objects, RRDP sessions and manifest anti-rollback metadata. Do not +share a writable database between processes. Changing `--out` changes default +state location unless `--rrdp-state-dir` is explicit. + +RRDP references and redirects enforce HTTPS and same-origin rules. Extra +trust roots do not relax origin checks. No separate rsync timeout is exposed. + +## Workers and queues + +| Option | Default | Meaning | +| --- | ---: | --- | +| `--parallel-max-repo-sync-workers-global ` | 8 | Global repository transport workers. | +| `--parallel-phase2-object-workers ` | 8 | Object workers, independent of transport. | +| `--parallel-phase2-worker-queue-capacity ` | 256 | Object-worker queue capacity. | +| `--parallel-repo-worker-queue-capacity ` | 256 | Pending repository results; effective limit is `max(n, 1024)`. | + +All values must be positive. Budgets are shared across TALs. Higher concurrency +can increase memory consumption and does not guarantee proportional speedup. + +## Logging + +| Option | Default | Values | +| --- | --- | --- | +| `--log-level ` | `info` | `off`, `error`, `warn`, `info`, `debug`, `trace` | +| `--log-format ` | `text` | `text`, `json` | + +Precedence: explicit option, then `PANDA_RPKI_LOG_LEVEL` or +`PANDA_RPKI_LOG_FORMAT`, then default. Each level includes more severe messages. +`off` suppresses logs but does not change exit codes. JSON logs contain +`timestamp`, `level`, `event`, and `fields`, one object per line. Logs use stderr; +help uses stdout; validation results are written to files. + +Other `PANDA_RPKI_*` variables in [Compose](docker.md) are template substitutions, +not configuration variables read directly by the binary. + +## Daemon + +Daemon runs in the foreground, launching this executable's `validate` command +in a separate process per cycle. Pass validator options after `--`, without +the word `validate`. The controller owns `--out`, `--ccr-out`, +`--rrdp-state-dir` and `--rrdp-sync-mode`; do not pass them after `--`. + +| Option | Default | Meaning | +| --- | --- | --- | +| `--state-root ` | Required | Dedicated root for state, lifecycle metadata and run outputs. | +| `--interval-secs ` | 60 | Wait after run recording and retention; zero starts the next run immediately. | +| `--max-runs ` | Unlimited | Positive attempt count for this invocation, including failed attempts. | +| `--retain-runs ` | 10 | Positive number of completed run directories retained, including recorded failures. Older completed directories are deleted. | +| `--run-timeout-secs ` | 0 | Whole-run deadline; zero disables. Sends SIGTERM to the child group, then SIGKILL after two seconds if needed. | +| `--shutdown-grace-secs ` | 30 | On SIGINT/SIGTERM, stop starting runs and wait at most n seconds before terminating the active process group. | + +Options cannot repeat. Seconds are nonnegative. Empty state starts with +snapshot, successful continuation requests delta, and recovery after a failed +run with existing state requests auto. Actual operations can include noop and +snapshot fallback. Normal child failures/timeouts are recorded and retried; +corrupt lifecycle metadata or inability to record state stops the controller. + +Restart continues numbering and reuses state. Retention leaves incomplete, +unrelated and symlink run directories untouched. It does not trim history JSONL. +Do not remove a live lock file. After SIGKILL, ensure the old validator child +has stopped before restarting. Stop the entire container for Docker deployments. + +## Outputs and exit status + +| File | Content | +| --- | --- | +| `summary.json` | TAL, worker, validation and actual RRDP operation counts. | +| `vrps.csv` | ROA payloads: `asn,prefix,max_length`. | +| `vaps.csv` | ASPA payloads. | +| `stage-timing.json`, `analysis/timing.json` | Timing and analysis counters. | +| `.vrps-source.csv` | Intermediate CSV used to produce `vrps.csv`. | +| Requested CCR path | Optional CCR DER; automatic per run in daemon mode. | + +Daemon adds `daemon.lock`, `daemon-status.json`, `lifecycle.json`, +`run-summary.jsonl`, `state/repository-db/`, and `runs/run_000001/` etc. +Each run includes validator outputs, `run-meta.json`, `run-summary.json`, +`stdout.log`, `stderr.log` and optional `process-time.txt`. `/usr/bin/time` +provides user/system CPU and peak RSS when installed; Docker includes it. +Controller states: `starting`, `running`, `sleeping`, `exited`, `failed`. + +Help and successful validation return 0; argument and propagated runtime +errors return 2. Completion can include rejected objects or recoverable +repository failures: inspect warnings and outputs too. Failed runs can leave +partial output; use a separate output directory per cycle. + +A bounded daemon returns 2 if any attempt failed, otherwise 0. Stopping during +an interval exits normally. A forcibly terminated current run is recorded as +failed and causes exit 2. diff --git a/docs/conformance-matrix.md b/docs/conformance-matrix.md new file mode 100644 index 0000000..d4e049b --- /dev/null +++ b/docs/conformance-matrix.md @@ -0,0 +1,21 @@ +# v0.1.0 conformance matrix + +| Behaviour | Standard reference | Implementation | Evidence | +| --- | --- | --- | --- | +| TAL syntax and TA public-key binding | RFC 8630 §2 | `src/validation/from_tal.rs`, `src/model/ta.rs` | parser and synthetic integration tests | +| TA and issued CA profile/signature/resource checks | RFC 6487 §4 | `src/validation/`, `src/model/` | profile unit tests and synthetic integration tests | +| Manifest, manifest file hashes, CRL freshness, and ROA checks | RFC 6486, RFC 6487, RFC 6482 | `src/validation/`, `src/model/` | RRDP unit tests and self-contained `tests/synthetic_docker_e2e.sh` | +| RRDP notification, snapshot, delta/replace/withdraw and fallback | RFC 8182 §§3.4–3.5 | `src/repository/sync/rrdp.rs`, `src/repository/sync/repo.rs` | snapshot/delta state-restart unit tests and synthetic Docker tests | +| RRDP direct-reference and redirect origin checks | RFC 9674 §3.2 | `src/repository/fetch/http.rs`, `src/repository/sync/rrdp.rs` | cross-origin unit tests | +| Repeated TAL/TA pairing and stable aggregate output | RFC 8630 §2; RFC 6813 data model | `src/cli/mod.rs`, `src/cli/validate.rs`, `src/runtime/` | CLI parser checks; locked multi-TAL E2E | +| Bounded repository and object workers (default 8 each, shared across TALs) | operational support for the standard validation pipeline | `src/scheduler/`, `src/runtime/` | worker-pool unit tests | +| Resumable RRDP protocol state | RFC 8182 §3.4 | `src/repository/storage/`, `src/repository/sync/rrdp.rs` | atomic state index and restart delta test | +| CCR manifest, ROA, ASPA, trust-anchor and router-key states | `draft-ietf-sidrops-rpki-ccr-11` §§2–4 | `src/ccr/` | encode/decode tests and integration artifacts | +| TA Constraints rule normalization and EE resource checks | `draft-ietf-sidrops-constraining-rpki-trust-anchors-01` §§3–4 | `src/ta_constraints.rs`, `src/validation/` | parser/normalization tests; constrained validation E2E | +| VRP CSV output | RFC 6810 data semantics | `src/cli/validate.rs`, `src/runtime/` | self-contained Docker E2E CSV/summary assertions | + +The worker pool and interval index are implementation mechanisms, not new wire +protocols; their tests must also prove deterministic output and unchanged +validation decisions. Only the table entries above are in v0.1.0 scope. +Adding a new source file or protocol feature requires a new row with a document +revision, section, test, and reviewer decision. diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..f8dea59 --- /dev/null +++ b/docs/development.md @@ -0,0 +1,33 @@ +# Development and testing + +Use Rust 1.92 or newer on Linux and the [native dependencies](getting-started.md). +Tests additionally need Python 3, Python `cryptography`, and OpenSSL's CLI. + +```bash +cargo fmt --all --check +cargo check --locked --no-default-features +cargo test --locked +cargo clippy --locked --all-targets -- -D warnings +cargo build --locked --release +bash tests/synthetic_docker_e2e.sh +``` + +Synthetic tests generate fresh keys, TALs, certificates and an HTTPS RRDP +repository. They cover snapshot, actual delta, persistence, daemon restart and +outputs without public RIR requests. Docker tests require Linux host networking. +Generated keys and outputs are temporary and unsuitable as production inputs. + +## Code organization + +- `src/cli/` parses arguments; `src/runtime/` coordinates validation. +- `src/repository/` handles transport, RRDP and persistence. +- `src/scheduler/` coordinates bounded workers. +- `src/model/` and `src/validation/` parse and validate RPKI objects. +- `src/ccr/` exports canonical state; `src/ta_constraints/` evaluates constraints. +- `src/daemon/` schedules runs; logging/output modules provide diagnostics. +- `tests/` contains integration tests and generated fixtures. +- `docker/` contains container and Compose deployment files. + +Internal Rust modules are not a stable library API. The supported interface is +the CLI and documented output files. Add behavior tests and update documentation +when changing these interfaces. diff --git a/docs/docker.md b/docs/docker.md new file mode 100644 index 0000000..e10651b --- /dev/null +++ b/docs/docker.md @@ -0,0 +1,84 @@ +# Docker and Compose + +Run commands from the project root. A Linux Docker engine is required for the +documented deployment and integration tests. + +```bash +docker build -f docker/Dockerfile -t panda-rpki:v0.1.0 . +``` + +The image defaults to an unprivileged user and includes CA certificates, +`rsync` and `/usr/bin/time`. Compose uses a read-only root filesystem and a +temporary `/tmp`. Host input mounts are read-only; state and outputs are writable. + +## Single anchor + +```bash +mkdir -p state output +export INPUT_DIR="$PWD/input" STATE_DIR="$PWD/state" OUTPUT_DIR="$PWD/output" +export TAL_FILE=anchor.tal TA_FILE=anchor.cer +export PUID="$(id -u)" PGID="$(id -g)" +docker compose -f docker/compose.yaml run --build --rm validator +``` + +Supply absolute host paths as above. Relative Compose paths are resolved from +the Compose file's directory, which is `docker/`. Build context is explicitly +the project root. `PANDA_RPKI_RRDP_SYNC_MODE` defaults to `auto`. + +## All five regional anchors + +Follow [Prepare all five RIR trust anchors](trust-anchors.md) to download and +check each TAL and matching certificate under `input/`, then run: + +```bash +mkdir -p state output +export INPUT_DIR="$PWD/input" STATE_DIR="$PWD/state" OUTPUT_DIR="$PWD/output" +export PUID="$(id -u)" PGID="$(id -g)" +export AFRINIC_TAL_FILE=afrinic.tal AFRINIC_TA_FILE=afrinic.cer +export APNIC_TAL_FILE=apnic.tal APNIC_TA_FILE=apnic.cer +export ARIN_TAL_FILE=arin.tal ARIN_TA_FILE=arin.cer +export LACNIC_TAL_FILE=lacnic.tal LACNIC_TA_FILE=lacnic.cer +export RIPE_TAL_FILE=ripe.tal RIPE_TA_FILE=ripe.cer +docker compose -f docker/compose.all5.yaml run --build --rm validator +``` + +| Template variable | Default | Applies to | +| --- | --- | --- | +| `PANDA_RPKI_WORKERS` | 8 | all5 and daemon object workers | +| `PANDA_RPKI_REPO_WORKERS` | 8 | all5 and daemon transport workers | +| `PANDA_RPKI_WORKER_QUEUE_CAPACITY` | 256 | all5 object queue | +| `PANDA_RPKI_HTTP_TIMEOUT_SECS` | 600 | all5 HTTP timeout (native CLI default is 300) | +| `PANDA_RPKI_RRDP_SYNC_MODE` | auto | single-anchor and all5 | + +The all5 CCR is `OUTPUT_DIR/all5.ccr`; the single-anchor CCR is +`OUTPUT_DIR/result.ccr`. Reuse `STATE_DIR` and select new `OUTPUT_DIR` values +to retain multiple cycles. These templates take no bundled RIR input files. + +## Daemon service + +```bash +mkdir -p data/daemon +export INPUT_DIR="$PWD/input" DAEMON_DIR="$PWD/data/daemon" +export TAL_FILE=anchor.tal TA_FILE=anchor.cer +export PUID="$(id -u)" PGID="$(id -g)" +export PANDA_RPKI_INTERVAL_SECS=600 PANDA_RPKI_RETAIN_RUNS=10 +docker compose -f docker/compose.daemon.yaml up -d --build +docker compose -f docker/compose.daemon.yaml logs -f validator +# Stop gracefully; persistent host data remains available. +docker compose -f docker/compose.daemon.yaml stop +``` + +Defaults: interval 60 seconds, retain 10, `PANDA_RPKI_RUN_TIMEOUT_SECS=0` +(disabled), log level `info`, log format `json`. The daemon template passes +`PANDA_RPKI_LOG_LEVEL` and `PANDA_RPKI_LOG_FORMAT` into the container. +It restarts unless stopped and allows 40 seconds for shutdown, exceeding the +controller's default 30-second grace plus termination wait. + +For multiple anchors, append TAL/TA pairs after `--` in the daemon service's +command. For other validator options, edit that same list. A finite run can +be invoked with `docker compose run --rm validator daemon ...` using the +[daemon CLI](command-line-reference.md#daemon); use `--max-runs` before `--`. + +Compose variables are expanded into arguments; only the two documented log +environment variables are read directly by the binary. For single-anchor/all5 +logs, pass them with `docker compose run -e PANDA_RPKI_LOG_LEVEL=debug ...`. diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..12b0e15 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,66 @@ +# Getting started + +Panda RPKI v0.1.0 is a Linux command-line application. You can use Docker without +installing Rust, or build a native binary. It requires outbound HTTPS to RPKI +repositories, a writable state directory, and enough disk space for repository +objects and outputs. Resource use depends on the selected trust anchors. + +## Prepare trust anchors + +A trust anchor defines what you trust. Obtain its TAL from the responsible RIR +and obtain the current DER certificate from a certificate URI in that TAL. +Panda RPKI checks that the certificate matches the TAL public key. It takes +both files as explicit inputs; it does not refresh the supplied TA file for you. + +For example, the [RIPE NCC trust-anchor page](https://www.ripe.net/manage-ips-and-asns/resource-management/rpki/ripe-ncc-rpki-trust-anchor-structure/) +publishes the RIPE NCC TAL. From the project root: + +```bash +mkdir -p input +curl --fail --location --proto '=https' --proto-redir '=https' \ + --connect-timeout 15 --max-time 120 \ + https://tal.rpki.ripe.net/ripe-ncc.tal -o input/anchor.tal +curl --fail --location --proto '=https' --proto-redir '=https' \ + --connect-timeout 15 --max-time 120 \ + https://rpki.ripe.net/ta/ripe-ncc-ta.cer -o input/anchor.cer +``` + +Check the TAL's current certificate URI before downloading. The +[RIR trust-anchor directory](https://www.ripe.net/manage-ips-and-asns/resource-management/rpki/rir-trust-anchor-statistics/) +links the other regional anchors. Use their official TALs and corresponding +certificate locations when preparing a multiple-anchor deployment. Store inputs +outside version control, keep TA certificates current, and retain local policy +files alongside the appropriate TALs. For all five RIRs, use the complete +[five-RIR input guide](trust-anchors.md), including official sources, download +commands, input checks, and the filenames used by Compose. + +## Docker first run + +Follow the [README Quick start](../README.md#quick-start). It creates +`output/summary.json`, `output/vrps.csv` and `output/result.ccr`. State is retained +under `state/repository-db/`. View warnings in the terminal and inspect the +summary to distinguish rejected objects from successful repository updates. + +## Build from source + +Install Rust 1.92 or newer. On Debian/Ubuntu, native build dependencies include: + +```bash +sudo apt-get update +sudo apt-get install --no-install-recommends build-essential clang libclang-dev \ + pkg-config ca-certificates rsync time +cargo build --locked --release +target/release/panda-rpki --help +``` + +Run with the same inputs: + +```bash +target/release/panda-rpki validate \ + --tal input/anchor.tal --ta input/anchor.cer \ + --rrdp-state-dir state --out output/native \ + --ccr-out output/native/result.ccr +``` + +For tests, install `python3-cryptography` and `openssl`. See +[Development](development.md) and [Usage](usage.md) for next steps. diff --git a/docs/third-party-licenses.txt b/docs/third-party-licenses.txt new file mode 100644 index 0000000..7754ce3 --- /dev/null +++ b/docs/third-party-licenses.txt @@ -0,0 +1,42111 @@ +Third-party license texts from locked package archives. + +======================================================================== +adler2 2.0.1 — LICENSE-0BSD +======================================================================== +Copyright (C) Jonas Schievink + +Permission to use, copy, modify, and/or distribute this software for +any purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +======================================================================== +adler2 2.0.1 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/LICENSE-2.0 + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +adler2 2.0.1 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +aho-corasick 1.1.5 — COPYING +======================================================================== +This project is dual-licensed under the Unlicense and MIT licenses. + +You may use this code under the terms of either license. + + +======================================================================== +aho-corasick 1.1.5 — LICENSE-MIT +======================================================================== +The MIT License (MIT) + +Copyright (c) 2015 Andrew Gallant + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +alloc-no-stdlib 2.0.4 — LICENSE +======================================================================== +Copyright (c) 2016 Dropbox, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +alloc-stdlib 0.2.4 — LICENSE (upstream package revision) +======================================================================== +Copyright (c) 2016 Dropbox, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +asn1-rs 0.7.2 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +asn1-rs 0.7.2 — LICENSE-MIT +======================================================================== +Copyright (c) 2017 Pierre Chifflier + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +asn1-rs-derive 0.6.0 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +asn1-rs-derive 0.6.0 — LICENSE-MIT +======================================================================== +Copyright (c) 2017 Pierre Chifflier + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +asn1-rs-impl 0.2.0 — LICENSE (upstream package revision) +======================================================================== +Copyright (c) 2017 Pierre Chifflier + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +async-compression 0.4.44 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +async-compression 0.4.44 — LICENSE-MIT +======================================================================== +The MIT License (MIT) + +Copyright (c) 2018 the rustasync developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +atomic-waker 1.1.2 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +atomic-waker 1.1.2 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +atomic-waker 1.1.2 — LICENSE-THIRD-PARTY +======================================================================== +=============================================================================== + +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +=============================================================================== + +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +autocfg 1.5.1 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +autocfg 1.5.1 — LICENSE-MIT +======================================================================== +Copyright (c) 2018 Josh Stone + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +base64 0.22.1 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +base64 0.22.1 — LICENSE-MIT +======================================================================== +The MIT License (MIT) + +Copyright (c) 2015 Alice Maz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +bindgen 0.69.5 — LICENSE +======================================================================== +BSD 3-Clause License + +Copyright (c) 2013, Jyun-Yan You +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +bitflags 2.13.1 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +bitflags 2.13.1 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +block-buffer 0.10.4 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +block-buffer 0.10.4 — LICENSE-MIT +======================================================================== +Copyright (c) 2018-2019 The RustCrypto Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +brotli 8.0.4 — LICENSE.BSD-3-Clause +======================================================================== +Copyright (c) 2016 Dropbox, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +brotli 8.0.4 — LICENSE.MIT +======================================================================== +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +brotli-decompressor 5.0.3 — LICENSE +======================================================================== +Copyright (c) 2016 Dropbox, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +bumpalo 3.20.3 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +bumpalo 3.20.3 — LICENSE-MIT +======================================================================== +Copyright (c) 2019 Nick Fitzgerald + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +bytes 1.12.1 — LICENSE +======================================================================== +Copyright (c) 2018 Carl Lerche + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +bzip2-sys 0.1.13+1.0.8 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +bzip2-sys 0.1.13+1.0.8 — LICENSE-MIT +======================================================================== +Copyright (c) 2014-2025 Alex Crichton and Contributors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +bzip2-sys 0.1.13+1.0.8 — bzip2-1.0.8/LICENSE +======================================================================== + +-------------------------------------------------------------------------- + +This program, "bzip2", the associated library "libbzip2", and all +documentation, are copyright (C) 1996-2019 Julian R Seward. All +rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The origin of this software must not be misrepresented; you must + not claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + +3. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original software. + +4. The name of the author may not be used to endorse or promote + products derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Julian Seward, jseward@acm.org +bzip2/libbzip2 version 1.0.8 of 13 July 2019 + +-------------------------------------------------------------------------- + + +======================================================================== +cc 1.4.4 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +cc 1.4.4 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 Alex Crichton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +cexpr 0.6.0 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +cexpr 0.6.0 — LICENSE-MIT +======================================================================== +(C) Copyright 2016 Jethro G. Beekman + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +cfg-if 1.0.4 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +cfg-if 1.0.4 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 Alex Crichton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +cfg_aliases 0.2.2 — LICENSE +======================================================================== +MIT License + +Copyright (c) 2020 Katharos Technology + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +cfg_aliases 0.2.2 — NOTICES.md +======================================================================== +# 3rd Party Notices + +The `cfg_aliases!` macro uses a lot of the code from [`tectonic_cfg_support::target_cfg!`] macro which is under the following license: + +[`tectonic_cfg_support::target_cfg!`]: https://github.com/tectonic-typesetting/tectonic/blob/f2439b936470ad27bdf92882064bc4702ee01899/cfg_support/src/lib.rs#L166 + + tectonic_cfg_support is licensed under the MIT License. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the “Software”), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +--- + + +======================================================================== +chacha20 0.10.2 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +chacha20 0.10.2 — LICENSE-MIT +======================================================================== +Copyright (c) 2019-2026 The RustCrypto Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +clang-sys 1.9.1 — LICENSE.txt +======================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +compression-codecs 0.4.39 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +compression-codecs 0.4.39 — LICENSE-MIT +======================================================================== +The MIT License (MIT) + +Copyright (c) 2018 the rustasync developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +compression-core 0.4.33 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +compression-core 0.4.33 — LICENSE-MIT +======================================================================== +The MIT License (MIT) + +Copyright (c) 2018 the rustasync developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +cookie-factory 0.3.3 — LICENSE (upstream package revision) +======================================================================== +Copyright (c) 2017-2023 Geoffroy Couprie + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +cpufeatures 0.2.17 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +cpufeatures 0.2.17 — LICENSE-MIT +======================================================================== +Copyright (c) 2020-2025 The RustCrypto Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +cpufeatures 0.3.1 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +cpufeatures 0.3.1 — LICENSE-MIT +======================================================================== +Copyright (c) 2020-2026 The RustCrypto Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +crc32fast 1.5.1 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +======================================================================== +crc32fast 1.5.1 — LICENSE-MIT +======================================================================== +MIT License + +Copyright (c) 2018 Sam Rijs, Alex Crichton and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +crypto-common 0.1.7 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +crypto-common 0.1.7 — LICENSE-MIT +======================================================================== +Copyright (c) 2021 RustCrypto Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +data-encoding 2.11.1 — LICENSE +======================================================================== +The MIT License (MIT) + +Copyright (c) 2015-2020 Julien Cretin +Copyright (c) 2017-2020 Google Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +der-parser 10.0.0 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +der-parser 10.0.0 — LICENSE-MIT +======================================================================== +Copyright (c) 2017 Pierre Chifflier + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +deranged 0.5.8 — LICENSE-Apache +======================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Jacob Pratt et al. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +deranged 0.5.8 — LICENSE-MIT +======================================================================== +Copyright (c) 2024 Jacob Pratt et al. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +digest 0.10.7 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +digest 0.10.7 — LICENSE-MIT +======================================================================== +Copyright (c) 2017 Artyom Pavlov + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +displaydoc 0.2.7 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +displaydoc 0.2.7 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +either 1.18.0 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +either 1.18.0 — LICENSE-MIT +======================================================================== +Copyright (c) 2015 + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +equivalent 1.0.2 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +equivalent 1.0.2 — LICENSE-MIT +======================================================================== +Copyright (c) 2016--2023 + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +errno 0.3.14 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +errno 0.3.14 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 Chris Wong + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +fastrand 2.5.0 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +fastrand 2.5.0 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +find-msvc-tools 0.1.11 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +find-msvc-tools 0.1.11 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 Alex Crichton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +flate2 1.1.10 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +flate2 1.1.10 — LICENSE-MIT +======================================================================== +Copyright (c) 2014-2026 Alex Crichton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +form_urlencoded 1.2.2 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +form_urlencoded 1.2.2 — LICENSE-MIT +======================================================================== +Copyright (c) 2013-2016 The rust-url developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +futures 0.3.34 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +futures 0.3.34 — LICENSE-MIT +======================================================================== +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +futures-channel 0.3.34 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +futures-channel 0.3.34 — LICENSE-MIT +======================================================================== +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +futures-core 0.3.34 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +futures-core 0.3.34 — LICENSE-MIT +======================================================================== +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +futures-executor 0.3.34 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +futures-executor 0.3.34 — LICENSE-MIT +======================================================================== +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +futures-io 0.3.34 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +futures-io 0.3.34 — LICENSE-MIT +======================================================================== +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +futures-macro 0.3.34 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +futures-macro 0.3.34 — LICENSE-MIT +======================================================================== +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +futures-sink 0.3.34 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +futures-sink 0.3.34 — LICENSE-MIT +======================================================================== +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +futures-task 0.3.34 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +futures-task 0.3.34 — LICENSE-MIT +======================================================================== +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +futures-util 0.3.34 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +futures-util 0.3.34 — LICENSE-MIT +======================================================================== +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +generic-array 0.14.7 — LICENSE +======================================================================== +The MIT License (MIT) + +Copyright (c) 2015 Bartłomiej Kamiński + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +======================================================================== +getrandom 0.2.17 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +getrandom 0.2.17 — LICENSE-MIT +======================================================================== +Copyright (c) 2018-2024 The rust-random Project Developers +Copyright (c) 2014 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +getrandom 0.4.3 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +getrandom 0.4.3 — LICENSE-MIT +======================================================================== +Copyright (c) 2018-2026 The rust-random Project Developers +Copyright (c) 2014 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +glob 0.3.4 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +glob 0.3.4 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +half 1.8.3 — LICENSE +======================================================================== +MIT OR Apache-2.0 + + +======================================================================== +half 1.8.3 — LICENSES/Apache-2.0.txt +======================================================================== +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +half 1.8.3 — LICENSES/MIT.txt +======================================================================== +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +hashbrown 0.17.1 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +hashbrown 0.17.1 — LICENSE-MIT +======================================================================== +Copyright (c) 2016 Amanieu d'Antras + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +hex 0.4.3 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +======================================================================== +hex 0.4.3 — LICENSE-MIT +======================================================================== +Copyright (c) 2013-2014 The Rust Project Developers. +Copyright (c) 2015-2020 The rust-hex Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +http 1.5.0 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2017 http-rs authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +http 1.5.0 — LICENSE-MIT +======================================================================== +Copyright (c) 2017 http-rs authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +http-body 1.1.0 — LICENSE +======================================================================== +Copyright (c) 2019-2026 Sean McArthur & Hyper Contributors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +http-body-util 0.1.5 — LICENSE +======================================================================== +Copyright (c) 2019-2026 Sean McArthur & Hyper Contributors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +httparse 1.10.1 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +httparse 1.10.1 — LICENSE-MIT +======================================================================== +Copyright (c) 2015-2025 Sean McArthur + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + + +======================================================================== +hyper 1.11.1 — LICENSE +======================================================================== +Copyright (c) 2014-2026 Sean McArthur + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +hyper-rustls 0.27.9 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +hyper-rustls 0.27.9 — LICENSE-ISC +======================================================================== +ISC License (ISC) +Copyright (c) 2016, Joseph Birr-Pixton + +Permission to use, copy, modify, and/or distribute this software for +any purpose with or without fee is hereby granted, provided that the +above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + + +======================================================================== +hyper-rustls 0.27.9 — LICENSE-MIT +======================================================================== +Copyright (c) 2016 Joseph Birr-Pixton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +hyper-util 0.1.20 — LICENSE +======================================================================== +Copyright (c) 2023-2025 Sean McArthur + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +icu_collections 2.3.0 — LICENSE +======================================================================== +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 2020-2024 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +SPDX-License-Identifier: Unicode-3.0 + +— + +Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. +ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. + + +======================================================================== +icu_locale_core 2.3.0 — LICENSE +======================================================================== +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 2020-2024 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +SPDX-License-Identifier: Unicode-3.0 + +— + +Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. +ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. + + +======================================================================== +icu_normalizer 2.3.0 — LICENSE +======================================================================== +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 2020-2024 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +SPDX-License-Identifier: Unicode-3.0 + +— + +Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. +ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. + + +======================================================================== +icu_normalizer_data 2.3.0 — LICENSE +======================================================================== +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 2020-2024 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +SPDX-License-Identifier: Unicode-3.0 + +— + +Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. +ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. + + +======================================================================== +icu_properties 2.3.0 — LICENSE +======================================================================== +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 2020-2024 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +SPDX-License-Identifier: Unicode-3.0 + +— + +Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. +ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. + + +======================================================================== +icu_properties_data 2.3.0 — LICENSE +======================================================================== +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 2020-2024 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +SPDX-License-Identifier: Unicode-3.0 + +— + +Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. +ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. + + +======================================================================== +icu_provider 2.3.1 — LICENSE +======================================================================== +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 2020-2024 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +SPDX-License-Identifier: Unicode-3.0 + +— + +Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. +ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. + + +======================================================================== +idna 1.1.0 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +idna 1.1.0 — LICENSE-MIT +======================================================================== +Copyright (c) 2013-2025 The rust-url developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +idna_adapter 1.2.2 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +idna_adapter 1.2.2 — LICENSE-MIT +======================================================================== +Copyright (c) The rust-url developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +indexmap 2.14.2 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +indexmap 2.14.2 — LICENSE-MIT +======================================================================== +Copyright (c) 2016--2017 + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +ipnet 2.12.1 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2017 Juniper Networks, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +ipnet 2.12.1 — LICENSE-MIT +======================================================================== +Copyright 2017 Juniper Networks, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +itertools 0.12.1 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +itertools 0.12.1 — LICENSE-MIT +======================================================================== +Copyright (c) 2015 + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +itoa 1.0.18 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + + +======================================================================== +itoa 1.0.18 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +jobserver 0.1.35 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +jobserver 0.1.35 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 Alex Crichton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +js-sys 0.3.104 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +js-sys 0.3.104 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 Alex Crichton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +lazy_static 1.5.0 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +lazy_static 1.5.0 — LICENSE-MIT +======================================================================== +Copyright (c) 2010 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +lazycell 1.3.0 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +lazycell 1.3.0 — LICENSE-MIT +======================================================================== +Original work Copyright (c) 2014 The Rust Project Developers +Modified work Copyright (c) 2016-2018 Nikita Pekin and lazycell contributors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +libc 0.2.189 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + + +======================================================================== +libc 0.2.189 — LICENSE-MIT +======================================================================== +Copyright (c) The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +libloading 0.8.9 — LICENSE +======================================================================== +Copyright © 2015, Simonas Kazlauskas + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without +fee is hereby granted, provided that the above copyright notice and this permission notice appear +in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS +SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + + +======================================================================== +librocksdb-sys 0.16.0+8.10.0 — rocksdb/AUTHORS +======================================================================== +Facebook Inc. +Facebook Engineering Team + +Google Inc. +# Initial version authors: +Jeffrey Dean +Sanjay Ghemawat + +# Partial list of contributors: +Kevin Regan +Johan Bilien +Matthew Von-Maszewski (Basho Technologies) + + +======================================================================== +librocksdb-sys 0.16.0+8.10.0 — rocksdb/COPYING +======================================================================== + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + + +======================================================================== +librocksdb-sys 0.16.0+8.10.0 — rocksdb/LICENSE.Apache +======================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +librocksdb-sys 0.16.0+8.10.0 — rocksdb/LICENSE.leveldb +======================================================================== +This contains code that is from LevelDB, and that code is under the following license: + +Copyright (c) 2011 The LevelDB Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +librocksdb-sys 0.16.0+8.10.0 — rocksdb/java/jmh/LICENSE-HEADER.txt +======================================================================== +Copyright (c) 2011-present, Facebook, Inc. All rights reserved. + This source code is licensed under both the GPLv2 (found in the + COPYING file in the root directory) and Apache 2.0 License + (found in the LICENSE.Apache file in the root directory). + + + +======================================================================== +librocksdb-sys 0.16.0+8.10.0 — rocksdb/utilities/transactions/lock/range/range_tree/lib/COPYING.AGPLv3 +======================================================================== + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. + + +======================================================================== +librocksdb-sys 0.16.0+8.10.0 — rocksdb/utilities/transactions/lock/range/range_tree/lib/COPYING.APACHEv2 +======================================================================== +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + +======================================================================== +librocksdb-sys 0.16.0+8.10.0 — rocksdb/utilities/transactions/lock/range/range_tree/lib/COPYING.GPLv2 +======================================================================== + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + + +======================================================================== +librocksdb-sys 0.16.0+8.10.0 — snappy/AUTHORS +======================================================================== +opensource@google.com + + +======================================================================== +librocksdb-sys 0.16.0+8.10.0 — snappy/COPYING +======================================================================== +Copyright 2011, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=== + +Some of the benchmark data in testdata/ is licensed differently: + + - fireworks.jpeg is Copyright 2013 Steinar H. Gunderson, and + is licensed under the Creative Commons Attribution 3.0 license + (CC-BY-3.0). See https://creativecommons.org/licenses/by/3.0/ + for more information. + + - kppkn.gtb is taken from the Gaviota chess tablebase set, and + is licensed under the MIT License. See + https://sites.google.com/site/gaviotachessengine/Home/endgame-tablebases-1 + for more information. + + - paper-100k.pdf is an excerpt (bytes 92160 to 194560) from the paper + “Combinatorial Modeling of Chromatin Features Quantitatively Predicts DNA + Replication Timing in _Drosophila_” by Federico Comoglio and Renato Paro, + which is licensed under the CC-BY license. See + http://www.ploscompbiol.org/static/license for more ifnormation. + + - alice29.txt, asyoulik.txt, plrabn12.txt and lcet10.txt are from Project + Gutenberg. The first three have expired copyrights and are in the public + domain; the latter does not have expired copyright, but is still in the + public domain according to the license information + (http://www.gutenberg.org/ebooks/53). + + +======================================================================== +libz-sys 1.1.29 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +libz-sys 1.1.29 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 Alex Crichton +Copyright (c) 2020 Josh Triplett + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +libz-sys 1.1.29 — src/zlib/LICENSE +======================================================================== +Copyright notice: + + (C) 1995-2026 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + + +======================================================================== +libz-sys 1.1.29 — src/zlib/contrib/dotzlib/LICENSE_1_0.txt +======================================================================== +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +======================================================================== +libz-sys 1.1.29 — src/zlib/contrib/minizip/LICENSE.Info-Zip +======================================================================== +This is version 2009-Jan-02 of the Info-ZIP license. The definitive version of +this document should be available at ftp://ftp.info- +zip.org/pub/infozip/license.html indefinitely and a copy at http://www.info- +zip.org/pub/infozip/license.html. + +Copyright (c) 1990-2009 Info-ZIP. All rights reserved. + +For the purposes of this copyright and license, "Info-ZIP" is defined as the +following set of individuals: + + Mark Adler, John Bush, Karl Davis, Harald Denker, Jean-Michel Dubois, Jean- + loup Gailly, Hunter Goatley, Ed Gordon, Ian Gorman, Chris Herborth, Dirk + Haase, Greg Hartwig, Robert Heath, Jonathan Hudson, Paul Kienitz, David + Kirschbaum, Johnny Lee, Onno van der Linden, Igor Mandrichenko, Steve P. + Miller, Sergio Monesi, Keith Owens, George Petrov, Greg Roelofs, Kai Uwe + Rommel, Steve Salisbury, Dave Smith, Steven M. Schweda, Christian Spieler, + Cosmin Truta, Antoine Verheijen, Paul von Behren, Rich Wales, Mike White. + +This software is provided "as is," without warranty of any kind, express or +implied. In no event shall Info-ZIP or its contributors be held liable for any +direct, indirect, incidental, special or consequential damages arising out of +the use of or inability to use this software. + +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the above disclaimer and the following restrictions: + + Redistributions of source code (in whole or in part) must retain the above + copyright notice, definition, disclaimer, and this list of conditions. + + Redistributions in binary form (compiled executables and libraries) must + reproduce the above copyright notice, definition, disclaimer, and this list + of conditions in documentation and/or other materials provided with the + distribution. Additional documentation is not needed for executables where a + command line license option provides these and a note regarding this option + is in the executable's startup banner. The sole exception to this condition + is redistribution of a standard UnZipSFX binary (including SFXWiz) as part + of a self-extracting archive; that is permitted without inclusion of this + license, as long as the normal SFX banner has not been removed from the + binary or disabled. + + Altered versions--including, but not limited to, ports to new operating + systems, existing ports with new graphical interfaces, versions with + modified or added functionality, and dynamic, shared, or static library + versions not from Info-ZIP--must be plainly marked as such and must not be + misrepresented as being the original source or, if binaries, compiled from + the original source. Such altered versions also must not be misrepresented + as being Info-ZIP releases--including, but not limited to, labeling of the + altered versions with the names "Info-ZIP" (or any variation thereof, + including, but not limited to, different capitalizations), "Pocket UnZip," + "WiZ" or "MacZip" without the explicit permission of Info-ZIP. Such altered + versions are further prohibited from misrepresentative use of the Zip-Bugs + or Info-ZIP e-mail addresses or the Info-ZIP URL(s), such as to imply Info- + ZIP will provide support for the altered versions. + + Info-ZIP retains the right to use the names "Info-ZIP," "Zip," "UnZip," + "UnZipSFX," "WiZ," "Pocket UnZip," "Pocket Zip," and "MacZip" for its own + source and binary releases. + + +======================================================================== +libz-sys 1.1.29 — src/zlib-ng/LICENSE.md +======================================================================== +(C) 1995-2024 Jean-loup Gailly and Mark Adler + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + + +======================================================================== +linux-raw-sys 0.12.1 — COPYRIGHT +======================================================================== +Short version for non-lawyers: + +`linux-raw-sys` is triple-licensed under Apache 2.0 with the LLVM Exception, +Apache 2.0, and MIT terms. + + +Longer version: + +Copyrights in the `linux-raw-sys` project are retained by their contributors. +No copyright assignment is required to contribute to the `linux-raw-sys` +project. + +Some files include code derived from Rust's `libstd`; see the comments in +the code for details. + +Except as otherwise noted (below and/or in individual files), `linux-raw-sys` +is licensed under: + + - the Apache License, Version 2.0, with the LLVM Exception + or + + - the Apache License, Version 2.0 + or + , + - or the MIT license + or + , + +at your option. + + +======================================================================== +linux-raw-sys 0.12.1 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +linux-raw-sys 0.12.1 — LICENSE-Apache-2.0_WITH_LLVM-exception +======================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +--- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + + + +======================================================================== +linux-raw-sys 0.12.1 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +litemap 0.8.3 — LICENSE +======================================================================== +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 2020-2024 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +SPDX-License-Identifier: Unicode-3.0 + +— + +Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. +ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. + + +======================================================================== +log 0.4.34 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +log 0.4.34 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +lru-slab 0.1.2 — LICENSE-APACHE +======================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +lru-slab 0.1.2 — LICENSE-MIT +======================================================================== +Copyright (c) 2024 The lru-slab Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +lru-slab 0.1.2 — LICENSE-ZLIB +======================================================================== +Copyright (c) 2024 The lru-slab Developers + +This software is provided 'as-is', without any express or implied warranty. In +no event will the authors be held liable for any damages arising from the use of +this software. + +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim + that you wrote the original software. If you use this software in a product, an + acknowledgment in the product documentation would be appreciated but is not + required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + + +======================================================================== +lz4-sys 1.11.1+lz4-1.10.0 — LICENSE +======================================================================== +The MIT License (MIT) + +Copyright (c) 2015 Artem V. Navrotskiy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + + +======================================================================== +lz4-sys 1.11.1+lz4-1.10.0 — liblz4/LICENSE +======================================================================== +This repository uses 2 different licenses : +- all files in the `lib` directory use a BSD 2-Clause license +- all other files use a GPL-2.0-or-later license, unless explicitly stated otherwise + +Relevant license is reminded at the top of each source file, +and with presence of COPYING or LICENSE file in associated directories. + +This model is selected to emphasize that +files in the `lib` directory are designed to be included into 3rd party applications, +while all other files, in `programs`, `tests` or `examples`, +are intended to be used "as is", as part of their intended scenarios, +with no intention to support 3rd party integration use cases. + + +======================================================================== +lz4-sys 1.11.1+lz4-1.10.0 — liblz4/contrib/debian/copyright +======================================================================== +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: liblz4 +Upstream-Contact: Yann Collet +Source: https://github.com/lz4/lz4 + +Files: * +Copyright: (C) 2011-2020 Yann Collet +License: GPL-2+ + The full text of license: https://github.com/lz4/lz4/blob/dev/lib/LICENSE + + +======================================================================== +lz4-sys 1.11.1+lz4-1.10.0 — liblz4/contrib/djgpp/LICENSE +======================================================================== +Copyright (c) 2014, lpsantil +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +======================================================================== +lz4-sys 1.11.1+lz4-1.10.0 — liblz4/examples/COPYING +======================================================================== +Full name: +GNU General Public License v2.0 or later + +Short identifier: +GPL-2.0-or-later + +Text: +GNU GENERAL PUBLIC LICENSE +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: +a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. +b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. +c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: +a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, +b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, +c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) +The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. +Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + +======================================================================== +lz4-sys 1.11.1+lz4-1.10.0 — liblz4/lib/LICENSE +======================================================================== +LZ4 Library +Copyright (c) 2011-2020, Yann Collet +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +lz4-sys 1.11.1+lz4-1.10.0 — liblz4/programs/COPYING +======================================================================== +Full name: +GNU General Public License v2.0 or later + +Short identifier: +GPL-2.0-or-later + +Text: +GNU GENERAL PUBLIC LICENSE +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: +a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. +b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. +c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: +a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, +b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, +c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) +The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. +Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + +======================================================================== +lz4-sys 1.11.1+lz4-1.10.0 — liblz4/tests/COPYING +======================================================================== +Full name: +GNU General Public License v2.0 or later + +Short identifier: +GPL-2.0-or-later + +Text: +GNU GENERAL PUBLIC LICENSE +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: +a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. +b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. +c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: +a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, +b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, +c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) +The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. +Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + +======================================================================== +memchr 2.8.3 — COPYING +======================================================================== +This project is dual-licensed under the Unlicense and MIT licenses. + +You may use this code under the terms of either license. + + +======================================================================== +memchr 2.8.3 — LICENSE-MIT +======================================================================== +The MIT License (MIT) + +Copyright (c) 2015 Andrew Gallant + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +minimal-lexical 0.2.1 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +minimal-lexical 0.2.1 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +minimal-lexical 0.2.1 — LICENSE.md +======================================================================== +Minimal-lexical is dual licensed under the Apache 2.0 license as well as the MIT +license. See the LICENCE-MIT and the LICENCE-APACHE files for the licenses. + +--- + +`src/bellerophon.rs` is loosely based off the Golang implementation, +found [here](https://github.com/golang/go/blob/b10849fbb97a2244c086991b4623ae9f32c212d0/src/strconv/extfloat.go). +That code (used if the `compact` feature is enabled) is subject to a +[3-clause BSD license](https://github.com/golang/go/blob/b10849fbb97a2244c086991b4623ae9f32c212d0/LICENSE): + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +miniz_oxide 0.9.1 — LICENSE +======================================================================== +MIT License + +Copyright 2013-2014 RAD Game Tools and Valve Software +Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC +Copyright (c) 2017 Frommi +Copyright (c) 2017-2024 oyvindln + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +miniz_oxide 0.9.1 — LICENSE-APACHE.md +======================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + +======================================================================== +miniz_oxide 0.9.1 — LICENSE-MIT.md +======================================================================== +MIT License + +Copyright 2013-2014 RAD Game Tools and Valve Software +Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC +Copyright (c) 2017 Frommi +Copyright (c) 2017-2024 oyvindln + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +miniz_oxide 0.9.1 — LICENSE-ZLIB.md +======================================================================== +Copyright 2013-2014 RAD Game Tools and Valve Software +Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC +Copyright (c) 2020 Frommi +Copyright (c) 2017-2024 oyvindln + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + + +======================================================================== +mio 1.2.3 — LICENSE +======================================================================== +Copyright (c) 2014 Carl Lerche and other MIO contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +nom 7.1.3 — LICENSE +======================================================================== +Copyright (c) 2014-2019 Geoffroy Couprie + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +num-bigint 0.4.8 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +num-bigint 0.4.8 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +num-conv 0.2.2 — LICENSE-Apache +======================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + +======================================================================== +num-conv 0.2.2 — LICENSE-MIT +======================================================================== +Copyright (c) Jacob Pratt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +num-integer 0.1.47 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +num-integer 0.1.47 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +num-traits 0.2.19 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +num-traits 0.2.19 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +oid-registry 0.8.1 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +oid-registry 0.8.1 — LICENSE-MIT +======================================================================== +Copyright (c) 2017 Pierre Chifflier + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +once_cell 1.21.4 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +once_cell 1.21.4 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +percent-encoding 2.3.2 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +percent-encoding 2.3.2 — LICENSE-MIT +======================================================================== +Copyright (c) 2013-2025 The rust-url developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +pin-project-lite 0.2.17 — LICENSE-APACHE +======================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + +======================================================================== +pin-project-lite 0.2.17 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +pkg-config 0.3.34 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +pkg-config 0.3.34 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 Alex Crichton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +potential_utf 0.1.6 — LICENSE +======================================================================== +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 2020-2024 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +SPDX-License-Identifier: Unicode-3.0 + +— + +Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. +ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. + + +======================================================================== +powerfmt 0.2.0 — LICENSE-Apache +======================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 Jacob Pratt et al. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +powerfmt 0.2.0 — LICENSE-MIT +======================================================================== +Copyright (c) 2023 Jacob Pratt et al. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +proc-macro2 1.0.107 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + + +======================================================================== +proc-macro2 1.0.107 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +quick-xml 0.37.5 — LICENSE-MIT.md +======================================================================== +The MIT License (MIT) + +Copyright (c) 2016 Johann Tuffe + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +quinn 0.11.11 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +quinn 0.11.11 — LICENSE-MIT +======================================================================== +Copyright (c) 2018 The quinn Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +quinn-proto 0.11.17 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +quinn-proto 0.11.17 — LICENSE-MIT +======================================================================== +Copyright (c) 2018 The quinn Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +quinn-udp 0.5.15 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +quinn-udp 0.5.15 — LICENSE-MIT +======================================================================== +Copyright (c) 2018 The quinn Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +quote 1.0.47 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + + +======================================================================== +quote 1.0.47 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +r-efi 6.0.0 — AUTHORS +======================================================================== +LICENSE: + This project is triple-licensed under the MIT License, the Apache + License, Version 2.0, and the GNU Lesser General Public License, + Version 2.1+. + +AUTHORS-MIT: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +AUTHORS-ASL: + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +AUTHORS-LGPL: + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; If not, see . + +COPYRIGHT: (ordered alphabetically) + Copyright (C) 2017-2023 Red Hat, Inc. + Copyright (C) 2019-2023 Microsoft Corporation + Copyright (C) 2022-2023 David Rheinsberg + +AUTHORS: (ordered alphabetically) + Alan Egerton + Alex James + Ayush Singh + Boris-Chengbiao Zhou + Bret Barkelew + Christopher Zurcher + David Rheinsberg + Dmitry Mostovenko + Hiroki Tokunaga + Joe Richey + John Schock + Michael Kubacki + Oliver Smith-Denny + Richard Wiedenhöft + Rob Bradford , + Tom Gundersen + Trevor Gross + + +======================================================================== +rand 0.10.2 — COPYRIGHT +======================================================================== +Copyrights in the Rand project are retained by their contributors. No +copyright assignment is required to contribute to the Rand project. + +For full authorship information, see the version control history. + +Except as otherwise noted (below and/or in individual files), Rand is +licensed under the Apache License, Version 2.0 or + or the MIT license + or , at your option. + +The Rand project includes code from the Rust project +published under these same licenses. + + +======================================================================== +rand 0.10.2 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + + +======================================================================== +rand 0.10.2 — LICENSE-MIT +======================================================================== +Copyright 2018 Developers of the Rand project +Copyright (c) 2014 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +rand_core 0.10.1 — COPYRIGHT +======================================================================== +Copyrights in the Rand project are retained by their contributors. No +copyright assignment is required to contribute to the Rand project. + +For full authorship information, see the version control history. + +Except as otherwise noted (below and/or in individual files), Rand is +licensed under the Apache License, Version 2.0 or + or the MIT license + or , at your option. + + +======================================================================== +rand_core 0.10.1 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + +======================================================================== +rand_core 0.10.1 — LICENSE-MIT +======================================================================== +Copyright (c) 2018-2026 The Rand Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +rand_pcg 0.10.2 — COPYRIGHT +======================================================================== +Copyrights in the Rand project are retained by their contributors. No +copyright assignment is required to contribute to the Rand project. + +For full authorship information, see the version control history. + +Except as otherwise noted (below and/or in individual files), Rand is +licensed under the Apache License, Version 2.0 or + or the MIT license + or , at your option. + +The Rand project includes code from the Rust project +published under these same licenses. + + +======================================================================== +rand_pcg 0.10.2 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + +======================================================================== +rand_pcg 0.10.2 — LICENSE-MIT +======================================================================== +Copyright (c) 2014-2017 Melissa O'Neill and PCG Project contributors +Copyright 2018 Developers of the Rand project + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +regex 1.13.1 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +regex 1.13.1 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +regex-automata 0.4.18 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +regex-automata 0.4.18 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +regex-syntax 0.8.11 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +regex-syntax 0.8.11 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +regex-syntax 0.8.11 — src/unicode_tables/LICENSE-UNICODE +======================================================================== +UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE + +Unicode Data Files include all data files under the directories +http://www.unicode.org/Public/, http://www.unicode.org/reports/, +http://www.unicode.org/cldr/data/, http://source.icu-project.org/repos/icu/, and +http://www.unicode.org/utility/trac/browser/. + +Unicode Data Files do not include PDF online code charts under the +directory http://www.unicode.org/Public/. + +Software includes any source code published in the Unicode Standard +or under the directories +http://www.unicode.org/Public/, http://www.unicode.org/reports/, +http://www.unicode.org/cldr/data/, http://source.icu-project.org/repos/icu/, and +http://www.unicode.org/utility/trac/browser/. + +NOTICE TO USER: Carefully read the following legal agreement. +BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S +DATA FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), +YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. +IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE +THE DATA FILES OR SOFTWARE. + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 1991-2018 Unicode, Inc. All rights reserved. +Distributed under the Terms of Use in http://www.unicode.org/copyright.html. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Unicode data files and any associated documentation +(the "Data Files") or Unicode software and any associated documentation +(the "Software") to deal in the Data Files or Software +without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, and/or sell copies of +the Data Files or Software, and to permit persons to whom the Data Files +or Software are furnished to do so, provided that either +(a) this copyright and permission notice appear with all copies +of the Data Files or Software, or +(b) this copyright and permission notice appear in associated +Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS +NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL +DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THE DATA FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in these Data Files or Software without prior +written authorization of the copyright holder. + + +======================================================================== +reqwest 0.12.28 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2016 Sean McArthur + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +reqwest 0.12.28 — LICENSE-MIT +======================================================================== +Copyright (c) 2016-2025 Sean McArthur + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + + +======================================================================== +ring 0.17.14 — LICENSE +======================================================================== +*ring* uses an "ISC" license, like BoringSSL used to use, for new code +files. See LICENSE-other-bits for the text of that license. + +See LICENSE-BoringSSL for code that was sourced from BoringSSL under the +Apache 2.0 license. Some code that was sourced from BoringSSL under the ISC +license. In each case, the license info is at the top of the file. + +See src/polyfill/once_cell/LICENSE-APACHE and src/polyfill/once_cell/LICENSE-MIT +for the license to code that was sourced from the once_cell project. + + +======================================================================== +ring 0.17.14 — LICENSE-BoringSSL +======================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Licenses for support code +------------------------- + +Parts of the TLS test suite are under the Go license. This code is not included +in BoringSSL (i.e. libcrypto and libssl) when compiled, however, so +distributing code linked against BoringSSL does not trigger this license: + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +BoringSSL uses the Chromium test infrastructure to run a continuous build, +trybots etc. The scripts which manage this, and the script for generating build +metadata, are under the Chromium license. Distributing code linked against +BoringSSL does not trigger this license. + +Copyright 2015 The Chromium Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +ring 0.17.14 — LICENSE-other-bits +======================================================================== +Copyright 2015-2025 Brian Smith. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +======================================================================== +ring 0.17.14 — src/polyfill/once_cell/LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +ring 0.17.14 — src/polyfill/once_cell/LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHOR OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +ring 0.17.14 — third_party/fiat/LICENSE +======================================================================== +The Apache License, Version 2.0 (Apache-2.0) + +Copyright 2015-2020 the fiat-crypto authors (see the AUTHORS file) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +rocksdb 0.22.0 — LICENSE +======================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +roxmltree 0.20.0 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +roxmltree 0.20.0 — LICENSE-MIT +======================================================================== +The MIT License (MIT) + +Copyright (c) 2018 Yevhenii Reizner + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +rustc-hash 1.1.0 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +rustc-hash 1.1.0 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +rustc-hash 2.1.3 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +======================================================================== +rustc-hash 2.1.3 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +======================================================================== +rusticata-macros 4.1.0 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +rusticata-macros 4.1.0 — LICENSE-MIT +======================================================================== +Copyright (c) 2017 Pierre Chifflier + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +rustix 1.1.4 — COPYRIGHT +======================================================================== +Short version for non-lawyers: + +`rustix` is triple-licensed under Apache 2.0 with the LLVM Exception, +Apache 2.0, and MIT terms. + + +Longer version: + +Copyrights in the `rustix` project are retained by their contributors. +No copyright assignment is required to contribute to the `rustix` +project. + +Some files include code derived from Rust's `libstd`; see the comments in +the code for details. + +Except as otherwise noted (below and/or in individual files), `rustix` +is licensed under: + + - the Apache License, Version 2.0, with the LLVM Exception + or + + - the Apache License, Version 2.0 + or + , + - or the MIT license + or + , + +at your option. + + +======================================================================== +rustix 1.1.4 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +rustix 1.1.4 — LICENSE-Apache-2.0_WITH_LLVM-exception +======================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +--- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + + + +======================================================================== +rustix 1.1.4 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +rustls 0.23.43 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +rustls 0.23.43 — LICENSE-ISC +======================================================================== +ISC License (ISC) +Copyright (c) 2016, Joseph Birr-Pixton + +Permission to use, copy, modify, and/or distribute this software for +any purpose with or without fee is hereby granted, provided that the +above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + + +======================================================================== +rustls 0.23.43 — LICENSE-MIT +======================================================================== +Copyright (c) 2016 Joseph Birr-Pixton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +rustls-pki-types 1.15.1 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2023 Dirkjan Ochtman + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +rustls-pki-types 1.15.1 — LICENSE-MIT +======================================================================== +Copyright (c) 2023 Dirkjan Ochtman + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +rustls-webpki 0.103.15 — LICENSE +======================================================================== +Except as otherwise noted, this project is licensed under the following +(ISC-style) terms: + +Copyright 2015 Brian Smith. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +The files under third-party/chromium are licensed as described in +third-party/chromium/LICENSE. + + +======================================================================== +rustversion 1.0.23 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + + +======================================================================== +rustversion 1.0.23 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +ryu 1.0.23 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + + +======================================================================== +ryu 1.0.23 — LICENSE-BOOST +======================================================================== +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +serde 1.0.229 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + + +======================================================================== +serde 1.0.229 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +serde_cbor 0.11.2 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +serde_cbor 0.11.2 — LICENSE-MIT +======================================================================== +Copyright (c) 2015 Pyfisch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +serde_core 1.0.229 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + + +======================================================================== +serde_core 1.0.229 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +serde_derive 1.0.229 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + + +======================================================================== +serde_derive 1.0.229 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +serde_json 1.0.151 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + + +======================================================================== +serde_json 1.0.151 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +serde_spanned 0.6.9 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +======================================================================== +serde_spanned 0.6.9 — LICENSE-MIT +======================================================================== +Copyright (c) Individual contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +serde_urlencoded 0.7.1 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + + +======================================================================== +serde_urlencoded 0.7.1 — LICENSE-MIT +======================================================================== +Copyright (c) 2016 Anthony Ramine + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +sha2 0.10.9 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +sha2 0.10.9 — LICENSE-MIT +======================================================================== +Copyright (c) 2006-2009 Graydon Hoare +Copyright (c) 2009-2013 Mozilla Foundation +Copyright (c) 2016 Artyom Pavlov + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +shlex 1.3.0 — LICENSE-APACHE +======================================================================== +Copyright 2015 Nicholas Allegra (comex). + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +shlex 1.3.0 — LICENSE-MIT +======================================================================== +The MIT License (MIT) + +Copyright (c) 2015 Nicholas Allegra (comex). + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +shlex 2.0.1 — LICENSE-APACHE +======================================================================== +Copyright 2015 Nicholas Allegra (comex). + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +shlex 2.0.1 — LICENSE-MIT +======================================================================== +The MIT License (MIT) + +Copyright (c) 2015 Nicholas Allegra (comex). + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +simd-adler32 0.3.10 — LICENSE.md +======================================================================== +MIT License + +Copyright (c) [2021] [Marvin Countryman] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +slab 0.4.12 — LICENSE +======================================================================== +Copyright (c) 2019 Carl Lerche + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +smallvec 1.16.0 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +smallvec 1.16.0 — LICENSE-MIT +======================================================================== +Copyright (c) 2018 The Servo Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +socket2 0.6.5 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +socket2 0.6.5 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 Alex Crichton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +stable_deref_trait 1.2.1 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +stable_deref_trait 1.2.1 — LICENSE-MIT +======================================================================== +Copyright (c) 2017 Robert Grosse + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +======================================================================== +subtle 2.6.1 — LICENSE +======================================================================== +Copyright (c) 2016-2017 Isis Agora Lovecruft, Henry de Valence. All rights reserved. +Copyright (c) 2016-2024 Isis Agora Lovecruft. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +syn 2.0.119 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + + +======================================================================== +syn 2.0.119 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +syn 3.0.4 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + + +======================================================================== +syn 3.0.4 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +sync_wrapper 1.0.2 — LICENSE +======================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + +======================================================================== +synstructure 0.13.2 — LICENSE +======================================================================== +Copyright 2016 Nika Layzell + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +tempfile 3.27.0 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +tempfile 3.27.0 — LICENSE-MIT +======================================================================== +Copyright (c) 2015 Steven Allen + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +thiserror 2.0.20 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + + +======================================================================== +thiserror 2.0.20 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +thiserror-impl 2.0.20 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + + +======================================================================== +thiserror-impl 2.0.20 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +time 0.3.55 — LICENSE-Apache +======================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + +======================================================================== +time 0.3.55 — LICENSE-MIT +======================================================================== +Copyright (c) Jacob Pratt et al. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +time-core 0.1.9 — LICENSE-Apache +======================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + +======================================================================== +time-core 0.1.9 — LICENSE-MIT +======================================================================== +Copyright (c) Jacob Pratt et al. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +time-macros 0.2.32 — LICENSE-Apache +======================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + +======================================================================== +time-macros 0.2.32 — LICENSE-MIT +======================================================================== +Copyright (c) Jacob Pratt et al. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +tinystr 0.8.4 — LICENSE +======================================================================== +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 2020-2024 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +SPDX-License-Identifier: Unicode-3.0 + +— + +Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. +ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. + + +======================================================================== +tinyvec 1.13.2 — LICENSE-APACHE.md +======================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +tinyvec 1.13.2 — LICENSE-MIT.md +======================================================================== +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +tinyvec 1.13.2 — LICENSE-ZLIB.md +======================================================================== +Copyright (c) 2019 Daniel "Lokathor" Gee. + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + + +======================================================================== +tinyvec_macros 0.1.1 — LICENSE-APACHE.md +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020 Tomasz "Soveu" Marx + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +======================================================================== +tinyvec_macros 0.1.1 — LICENSE-MIT.md +======================================================================== +MIT License + +Copyright (c) 2020 Soveu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +tinyvec_macros 0.1.1 — LICENSE-ZLIB.md +======================================================================== +zlib License + +(C) 2020 Tomasz "Soveu" Marx + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + + + +======================================================================== +tokio 1.53.1 — LICENSE +======================================================================== +MIT License + +Copyright (c) Tokio Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +tokio-rustls 0.26.4 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2017 quininer kel + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +tokio-rustls 0.26.4 — LICENSE-MIT +======================================================================== +Copyright (c) 2017 quininer kel + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +tokio-util 0.7.19 — LICENSE +======================================================================== +MIT License + +Copyright (c) Tokio Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +toml 0.8.23 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +======================================================================== +toml 0.8.23 — LICENSE-MIT +======================================================================== +Copyright (c) Individual contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +toml_datetime 0.6.11 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +======================================================================== +toml_datetime 0.6.11 — LICENSE-MIT +======================================================================== +Copyright (c) Individual contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +toml_edit 0.22.27 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +======================================================================== +toml_edit 0.22.27 — LICENSE-MIT +======================================================================== +Copyright (c) Individual contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +toml_write 0.1.2 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +======================================================================== +toml_write 0.1.2 — LICENSE-MIT +======================================================================== +Copyright (c) Individual contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +tower 0.5.3 — LICENSE +======================================================================== +Copyright (c) 2019 Tower Contributors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +tower-http 0.6.11 — LICENSE +======================================================================== +Copyright (c) 2019-2021 Tower Contributors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +tower-layer 0.3.3 — LICENSE +======================================================================== +Copyright (c) 2019 Tower Contributors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +tower-service 0.3.3 — LICENSE +======================================================================== +Copyright (c) 2019 Tower Contributors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +tracing 0.1.44 — LICENSE +======================================================================== +Copyright (c) 2019 Tokio Contributors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +tracing-core 0.1.36 — LICENSE +======================================================================== +Copyright (c) 2019 Tokio Contributors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +tracing-core 0.1.36 — src/spin/LICENSE +======================================================================== +The MIT License (MIT) + +Copyright (c) 2014 Mathijs van de Nes + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +try-lock 0.2.5 — LICENSE +======================================================================== +Copyright (c) 2018-2023 Sean McArthur +Copyright (c) 2016 Alex Crichton + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + + +======================================================================== +typenum 1.20.1 — LICENSE +======================================================================== +MIT OR Apache-2.0 + +======================================================================== +typenum 1.20.1 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2014 Paho Lurie-Gregg + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +======================================================================== +typenum 1.20.1 — LICENSE-MIT +======================================================================== +The MIT License (MIT) + +Copyright (c) 2014 Paho Lurie-Gregg + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +unicode-ident 1.0.24 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + + +======================================================================== +unicode-ident 1.0.24 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +unicode-ident 1.0.24 — LICENSE-UNICODE +======================================================================== +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 1991-2023 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + + +======================================================================== +untrusted 0.9.0 — LICENSE.txt +======================================================================== +// Copyright 2015-2016 Brian Smith. +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +======================================================================== +url 2.5.8 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +url 2.5.8 — LICENSE-MIT +======================================================================== +Copyright (c) 2013-2025 The rust-url developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +utf8_iter 1.0.4 — COPYRIGHT +======================================================================== +Copyright Mozilla Foundation + +Licensed under the Apache License (Version 2.0), or the MIT license, +(the "Licenses") at your option. You may not use this file except in +compliance with one of the Licenses. You may obtain copies of the +Licenses at: + + https://www.apache.org/licenses/LICENSE-2.0 + https://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software +distributed under the Licenses is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the Licenses for the specific language governing permissions and +limitations under the Licenses. + +-- + +Test code is dedicated to the Public Domain when so designated (see +the individual files for PD/CC0-dedicated sections). + +-- + +The implementation for Utf8CharIndices was adapted from the +CharIndices implementation of the Rust standard library at revision +ab32548539ec38a939c1b58599249f3b54130026 +(https://github.com/rust-lang/rust/blob/ab32548539ec38a939c1b58599249f3b54130026/library/core/src/str/iter.rs). + +Excerpt from https://github.com/rust-lang/rust/blob/ab32548539ec38a939c1b58599249f3b54130026/COPYRIGHT , +which refers to +https://github.com/rust-lang/rust/blob/ab32548539ec38a939c1b58599249f3b54130026/LICENSE-APACHE +and +https://github.com/rust-lang/rust/blob/ab32548539ec38a939c1b58599249f3b54130026/LICENSE-MIT +: + +For full authorship information, see the version control history or +https://thanks.rust-lang.org + +Except as otherwise noted (below and/or in individual files), Rust is +licensed under the Apache License, Version 2.0 or + or the MIT license + or , at your option. + + +======================================================================== +utf8_iter 1.0.4 — LICENSE-APACHE +======================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +utf8_iter 1.0.4 — LICENSE-MIT +======================================================================== +Copyright Mozilla Foundation + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +uuid 1.26.0 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +uuid 1.26.0 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 The Rust Project Developers +Copyright (c) 2018 Ashley Mannix, Christopher Armstrong, Dylan DPC, Hunar Roop Kahlon + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +vcpkg 0.2.15 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + + +======================================================================== +vcpkg 0.2.15 — LICENSE-MIT +======================================================================== +Copyright (c) 2017 Jim McGrath + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + + +======================================================================== +version_check 0.9.5 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +version_check 0.9.5 — LICENSE-MIT +======================================================================== +The MIT License (MIT) +Copyright (c) 2017-2018 Sergio Benitez + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +want 0.3.1 — LICENSE +======================================================================== +Copyright (c) 2018-2019 Sean McArthur + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + + +======================================================================== +wasi 0.11.1+wasi-snapshot-preview1 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +wasi 0.11.1+wasi-snapshot-preview1 — LICENSE-Apache-2.0_WITH_LLVM-exception +======================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +--- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + + + +======================================================================== +wasi 0.11.1+wasi-snapshot-preview1 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +wasm-bindgen 0.2.127 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +wasm-bindgen 0.2.127 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 Alex Crichton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +wasm-bindgen-futures 0.4.77 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +wasm-bindgen-futures 0.4.77 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 Alex Crichton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +wasm-bindgen-macro 0.2.127 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +wasm-bindgen-macro 0.2.127 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 Alex Crichton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +wasm-bindgen-macro-support 0.2.127 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +wasm-bindgen-macro-support 0.2.127 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 Alex Crichton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +wasm-bindgen-shared 0.2.127 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +wasm-bindgen-shared 0.2.127 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 Alex Crichton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +web-sys 0.3.104 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +web-sys 0.3.104 — LICENSE-MIT +======================================================================== +Copyright (c) 2014 Alex Crichton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +web-time 1.1.0 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 dAxpeDDa + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +web-time 1.1.0 — LICENSE-MIT +======================================================================== +MIT License + +Copyright (c) 2023 dAxpeDDa + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +webpki-roots 1.0.9 — LICENSE +======================================================================== +# Community Data License Agreement - Permissive - Version 2.0 + +This is the Community Data License Agreement - Permissive, Version +2.0 (the "agreement"). Data Provider(s) and Data Recipient(s) agree +as follows: + +## 1. Provision of the Data + +1.1. A Data Recipient may use, modify, and share the Data made +available by Data Provider(s) under this agreement if that Data +Recipient follows the terms of this agreement. + +1.2. This agreement does not impose any restriction on a Data +Recipient's use, modification, or sharing of any portions of the +Data that are in the public domain or that may be used, modified, +or shared under any other legal exception or limitation. + +## 2. Conditions for Sharing Data + +2.1. A Data Recipient may share Data, with or without modifications, so +long as the Data Recipient makes available the text of this agreement +with the shared Data. + +## 3. No Restrictions on Results + +3.1. This agreement does not impose any restriction or obligations +with respect to the use, modification, or sharing of Results. + +## 4. No Warranty; Limitation of Liability + +4.1. All Data Recipients receive the Data subject to the following +terms: + +THE DATA IS PROVIDED ON AN "AS IS" BASIS, WITHOUT REPRESENTATIONS, +WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED +INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +NO DATA PROVIDER SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING +WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE DATA OR RESULTS, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +## 5. Definitions + +5.1. "Data" means the material received by a Data Recipient under +this agreement. + +5.2. "Data Provider" means any person who is the source of Data +provided under this agreement and in reliance on a Data Recipient's +agreement to its terms. + +5.3. "Data Recipient" means any person who receives Data directly +or indirectly from a Data Provider and agrees to the terms of this +agreement. + +5.4. "Results" means any outcome obtained by computational analysis +of Data, including for example machine learning models and models' +insights. + + +======================================================================== +windows-link 0.2.1 — license-apache-2.0 +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) Microsoft Corporation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +windows-link 0.2.1 — license-mit +======================================================================== + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +======================================================================== +windows-sys 0.52.0 — license-apache-2.0 +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) Microsoft Corporation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +windows-sys 0.52.0 — license-mit +======================================================================== + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +======================================================================== +windows-sys 0.61.2 — license-apache-2.0 +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) Microsoft Corporation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +windows-sys 0.61.2 — license-mit +======================================================================== + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +======================================================================== +windows-targets 0.52.6 — license-apache-2.0 +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) Microsoft Corporation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +windows-targets 0.52.6 — license-mit +======================================================================== + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +======================================================================== +windows_aarch64_gnullvm 0.52.6 — license-apache-2.0 +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) Microsoft Corporation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +windows_aarch64_gnullvm 0.52.6 — license-mit +======================================================================== + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +======================================================================== +windows_aarch64_msvc 0.52.6 — license-apache-2.0 +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) Microsoft Corporation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +windows_aarch64_msvc 0.52.6 — license-mit +======================================================================== + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +======================================================================== +windows_i686_gnu 0.52.6 — license-apache-2.0 +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) Microsoft Corporation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +windows_i686_gnu 0.52.6 — license-mit +======================================================================== + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +======================================================================== +windows_i686_gnullvm 0.52.6 — license-apache-2.0 +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) Microsoft Corporation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +windows_i686_gnullvm 0.52.6 — license-mit +======================================================================== + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +======================================================================== +windows_i686_msvc 0.52.6 — license-apache-2.0 +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) Microsoft Corporation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +windows_i686_msvc 0.52.6 — license-mit +======================================================================== + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +======================================================================== +windows_x86_64_gnu 0.52.6 — license-apache-2.0 +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) Microsoft Corporation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +windows_x86_64_gnu 0.52.6 — license-mit +======================================================================== + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +======================================================================== +windows_x86_64_gnullvm 0.52.6 — license-apache-2.0 +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) Microsoft Corporation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +windows_x86_64_gnullvm 0.52.6 — license-mit +======================================================================== + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +======================================================================== +windows_x86_64_msvc 0.52.6 — license-apache-2.0 +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) Microsoft Corporation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +windows_x86_64_msvc 0.52.6 — license-mit +======================================================================== + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +======================================================================== +winnow 0.7.15 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +writeable 0.6.4 — LICENSE +======================================================================== +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 2020-2024 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +SPDX-License-Identifier: Unicode-3.0 + +— + +Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. +ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. + + +======================================================================== +x509-parser 0.18.1 — LICENSE-APACHE +======================================================================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +======================================================================== +x509-parser 0.18.1 — LICENSE-MIT +======================================================================== +Copyright (c) 2017 Pierre Chifflier + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +yoke 0.8.3 — LICENSE +======================================================================== +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 2020-2024 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +SPDX-License-Identifier: Unicode-3.0 + +— + +Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. +ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. + + +======================================================================== +yoke-derive 0.8.2 — LICENSE +======================================================================== +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 2020-2024 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +SPDX-License-Identifier: Unicode-3.0 + +— + +Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. +ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. + + +======================================================================== +zerofrom 0.1.8 — LICENSE +======================================================================== +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 2020-2024 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +SPDX-License-Identifier: Unicode-3.0 + +— + +Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. +ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. + + +======================================================================== +zerofrom-derive 0.1.7 — LICENSE +======================================================================== +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 2020-2024 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +SPDX-License-Identifier: Unicode-3.0 + +— + +Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. +ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. + + +======================================================================== +zeroize 1.9.0 — LICENSE-APACHE +======================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +zeroize 1.9.0 — LICENSE-MIT +======================================================================== +Copyright (c) 2018-2026 The RustCrypto Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +zerotrie 0.2.5 — LICENSE +======================================================================== +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 2020-2024 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +SPDX-License-Identifier: Unicode-3.0 + +— + +Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. +ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. + + +======================================================================== +zerovec 0.11.8 — LICENSE +======================================================================== +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 2020-2024 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +SPDX-License-Identifier: Unicode-3.0 + +— + +Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. +ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. + + +======================================================================== +zerovec-derive 0.11.6 — LICENSE +======================================================================== +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 2020-2024 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +SPDX-License-Identifier: Unicode-3.0 + +— + +Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. +ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. + + +======================================================================== +zlib-rs 0.6.7 — LICENSE +======================================================================== +(C) 2024 Trifecta Tech Foundation + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + + +======================================================================== +zmij 1.0.23 — LICENSE-MIT +======================================================================== +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + diff --git a/docs/trust-anchors.md b/docs/trust-anchors.md new file mode 100644 index 0000000..11767cc --- /dev/null +++ b/docs/trust-anchors.md @@ -0,0 +1,148 @@ +# Prepare all five RIR trust anchors + +Panda RPKI requires a TAL and a matching DER-encoded TA certificate for each +anchor. The TAL supplies the trusted public key; the certificate supplies the +current trust-anchor certificate. A certificate alone is not a substitute for +an independently obtained TAL. No RIR inputs are bundled with this project. + +## Official sources + +Use the five ordinary production anchors below. Separate AS0 and test anchors +are not part of this all-five example. Review the applicable RIR terms before +using their services, including the [ARIN TAL page](https://www.arin.net/resources/manage/rpki/tal/) +and its linked [Relying Party Agreement](https://www.arin.net/resources/manage/rpki/rpa.pdf). +The project's BSD license does not replace those terms. + +| RIR | Official TAL download | TA certificate location | Local files | +| --- | --- | --- | --- | +| AFRINIC | [afrinic.tal](https://rpki.afrinic.net/tal/afrinic.tal) | `https://rpki.afrinic.net/repository/AfriNIC.cer` | `afrinic.tal`, `afrinic.cer` | +| APNIC | [apnic.tal](https://tal.apnic.net/apnic.tal) | `rsync://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer` | `apnic.tal`, `apnic.cer` | +| ARIN | [arin.tal](https://www.arin.net/resources/manage/rpki/arin.tal) | `https://rrdp.arin.net/arin-rpki-ta.cer` | `arin.tal`, `arin.cer` | +| LACNIC | [lacnic.tal (official file endpoint)](https://www.lacnic.net/innovaportal/file/4983/1/lacnic.tal) | `https://rrdp.lacnic.net/ta/rta-lacnic-rpki.cer` | `lacnic.tal`, `lacnic.cer` | +| RIPE NCC | [ripe-ncc.tal](https://tal.rpki.ripe.net/ripe-ncc.tal) | `https://rpki.ripe.net/ta/ripe-ncc-ta.cer` | `ripe.tal`, `ripe.cer` | + +Further official references: [APNIC TAL archive](https://www.apnic.net/community/security/resource-certification/tal-archive/), +[LACNIC trust anchors](https://www.lacnic.net/4984/2/lacnic/rpki-rpki-trust-anchor), +and [RIPE NCC trust-anchor structure](https://www.ripe.net/manage-ips-and-asns/resource-management/rpki/ripe-ncc-rpki-trust-anchor-structure/). +Locations can change: inspect the downloaded TAL before using a certificate URL +from this table. Do not rewrite TAL public keys or invent certificate URLs. + +## Download the TAL files + +Requirements: Bash, curl, OpenSSL, and rsync. On Debian/Ubuntu: + +```bash +sudo apt-get install --no-install-recommends ca-certificates curl openssl rsync +``` + +Run from the project root, in a Bash shell. Use a fresh input directory when +updating an existing deployment; the commands below overwrite matching filenames. + +```bash +set -euo pipefail +mkdir -p input +fetch_https() { + curl --fail --show-error --location \ + --proto '=https' --proto-redir '=https' \ + --connect-timeout 15 --max-time 120 --retry 2 --retry-max-time 300 \ + "$1" --output "$2" +} +fetch_https https://rpki.afrinic.net/tal/afrinic.tal input/afrinic.tal +fetch_https https://tal.apnic.net/apnic.tal input/apnic.tal +fetch_https https://www.arin.net/resources/manage/rpki/arin.tal input/arin.tal +fetch_https https://www.lacnic.net/innovaportal/file/4983/1/lacnic.tal input/lacnic.tal +fetch_https https://tal.rpki.ripe.net/ripe-ncc.tal input/ripe.tal +``` + +An HTTP 200 response does not prove that a file is a TAL. During a documentation +check on 2026-09-09, LACNIC's short download URL +`https://www.lacnic.net/rpki/lacnic.tal` returned an HTML website instead; +the official file endpoint used above returned a TAL. If either download +stops working, open the official LACNIC trust-anchor page above and +save its **ordinary production TAL** text as `input/lacnic.tal`: URI lines, +one blank line, then the complete Base64 public key. Do not copy the separate +AS0 TAL, page markup, or a certificate-derived key. If the official TAL text is +unavailable, stop and obtain it from LACNIC; do not substitute an arbitrary mirror. + +Check all five TALs before downloading certificates: + +```bash +for rir in afrinic apnic arin lacnic ripe; do + tal="input/$rir.tal" + if grep -Eiq '&2 + exit 1 + fi + grep -E '^(https|rsync)://' "$tal" + awk ' + { sub(/\r$/, "") } + /^#/ { next } + /^(https|rsync):\/\// { uri=1; next } + /^[[:space:]]*$/ { if (uri) key=1; next } + key { printf "%s", $0 } + ' "$tal" | tr -d '[:space:]' | openssl base64 -d -A \ + | openssl pkey -pubin -inform DER -noout +done +``` + +## Download the TA certificates + +After checking that the URI lines match the table, run in the same Bash shell +(which defines `fetch_https`): + +```bash +fetch_https https://rpki.afrinic.net/repository/AfriNIC.cer input/afrinic.cer +timeout 120 rsync --timeout=60 --contimeout=15 \ + rsync://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer input/apnic.cer +fetch_https https://rrdp.arin.net/arin-rpki-ta.cer input/arin.cer +fetch_https https://rrdp.lacnic.net/ta/rta-lacnic-rpki.cer input/lacnic.cer +fetch_https https://rpki.ripe.net/ta/ripe-ncc-ta.cer input/ripe.cer +``` + +APNIC's TAL currently lists only rsync, which requires outbound TCP port 873 +for this bootstrap step. This does not change Panda RPKI's HTTPS RRDP sync +mode. APNIC also serves the certificate at the following HTTPS endpoint, +which can be used when port 873 is blocked; this is an alternative download +endpoint, not an HTTPS URI present in the current APNIC TAL: + +```bash +fetch_https https://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer input/apnic.cer +``` + +Always verify the downloaded certificate against the independently obtained +TAL, including when using this alternative endpoint. + +## Check the pairs and run + +The following checks DER parsing, expiration, and equality of the certificate's +SubjectPublicKeyInfo with the TAL key. They are input sanity checks, not a full +RPKI profile or chain validation; Panda RPKI performs its validation during a run. + +```bash +for rir in afrinic apnic arin lacnic ripe; do + openssl x509 -inform DER -in "input/$rir.cer" -noout -subject -dates + openssl x509 -inform DER -in "input/$rir.cer" -noout -checkend 0 + tal_key=$(awk ' + { sub(/\r$/, "") } + /^#/ { next } + /^(https|rsync):\/\// { uri=1; next } + /^[[:space:]]*$/ { if (uri) key=1; next } + key { printf "%s", $0 } + ' "input/$rir.tal" | tr -d '[:space:]') + cert_key=$(openssl x509 -inform DER -in "input/$rir.cer" -pubkey -noout \ + | openssl pkey -pubin -outform DER | openssl base64 -A) + test -n "$tal_key" && test "$tal_key" = "$cert_key" + echo "$rir: TAL/TA public keys match" +done +``` + +You now have ten files in `input/`, named exactly as expected by the +[all-five Compose example](docker.md#all-five-regional-anchors). For the +single-anchor README example only, copy a chosen pair to `input/anchor.tal` +and `input/anchor.cer`, or change the CLI arguments to use its RIR filenames. + +Keep inputs out of version control. Monitor certificate validity and RIR +announcements; Panda RPKI does not refresh supplied TA files automatically. +Stage and check updated pairs before replacing active inputs between runs. +Never resolve a key mismatch by replacing the TAL key with the downloaded +certificate's key: retrieve the current TAL from the RIR and investigate first. diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..b90096c --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,101 @@ +# Usage + +Examples assume `panda-rpki` is on PATH; for a local build use +`target/release/panda-rpki`. Prepare TAL/TA files as described in +[Getting started](getting-started.md). + +## Snapshot and delta + +Use a fresh state directory for an explicit snapshot: + +```bash +panda-rpki validate --tal input/anchor.tal --ta input/anchor.cer \ + --rrdp-state-dir state/anchor --rrdp-sync-mode snapshot \ + --out output/snapshot --ccr-out output/snapshot/result.ccr +panda-rpki validate --tal input/anchor.tal --ta input/anchor.cer \ + --rrdp-state-dir state/anchor --rrdp-sync-mode delta \ + --out output/delta-01 --ccr-out output/delta-01/result.ccr +``` + +Subsequent runs reuse state and can use `auto`. Delta mode can include a +protocol-required snapshot or noop. Check actual operation counters in the +summary. Do not discard persistent state between scheduled runs. + +## Multiple anchors and constraints + +```bash +panda-rpki validate \ + --tal input/arin.tal --ta input/arin.cer --tal-id arin \ + --tal input/ripe.tal --ta input/ripe.cer --tal-id ripe \ + --parallel-max-repo-sync-workers-global 8 \ + --parallel-phase2-object-workers 8 \ + --rrdp-state-dir state/multi --out output/multi \ + --ccr-out output/multi/result.ccr +``` + +Add further pairs in the same way. Optional resource constraints are read from +the TAL-adjacent `.constraints` file, or specified with +`--ta-constraints arin=input/arin.constraints`. Rules follow the WG profile +listed in the [standards matrix](conformance-matrix.md). + +A constraint file contains `allow`/`deny` followed by an IPv4/IPv6 prefix, +address range, ASN or ASN range. Blank lines and `#` comments are allowed. +For a synthetic test anchor, an example is: + +```text +# Example resources only: do not apply this policy to a production RIR. +allow 192.0.2.0/24 +deny 192.0.2.128/25 +allow 2001:db8::/32 +allow 64496 - 64511 +``` + +Deny overlap rejects an EE resource; otherwise the resource must be fully +covered by allow rules of its resource family. Unlisted resources are not +implicitly allowed. An empty constraint file therefore differs from having +no constraint file. Overlapping rules are normalized and may produce warnings. + +## Continuous operation + +```bash +panda-rpki daemon --state-root data/daemon \ + --interval-secs 600 --retain-runs 10 -- \ + --tal input/anchor.tal --ta input/anchor.cer \ + --log-level info --log-format json +``` + +The daemon runs in the foreground. It starts with snapshot for empty state and +requests delta after successful runs. The interval starts after each cycle +finishes. Add `--max-runs 3` before `--` for a finite run. SIGTERM requests a +graceful stop. Restart with the same root to continue numbering and state. + +Each cycle writes to `data/daemon/runs/run_000001/`, then `run_000002/`, etc. +Retention deletes the oldest completed run directories beyond the configured +limit, including their artifacts. Copy anything you need to retain elsewhere. +The append-only summary history is not trimmed by retention. + +## Outputs and diagnostics + +`vrps.csv` contains `asn,prefix,max_length`. CCR contains canonical state; +duplicate routing payloads can collapse into a single CCR entry. Count raw +rows and normalized unique payloads separately when comparing outputs. +ASPA canonicalization groups providers by customer ASN. + +Use `--log-level debug` for diagnosis, or `trace` for detailed object/worker +events. Logs are written to stderr and can be redirected independently from +outputs. In daemon mode, inspect per-run `stderr.log` and the controller's +`daemon-status.json` and `run-summary.jsonl`. + +| Symptom | Action | +| --- | --- | +| TAL/TA key mismatch or expired TA | Refresh the certificate from the TAL's official URI and check the selected pair. | +| State-layout/schema error | Preserve the directory and select a new dedicated root for a compatible run. | +| Snapshot refuses existing state | Use `auto`/`delta`, or choose a fresh directory for a new snapshot. | +| Delta refuses empty state | Establish state with `auto` or `snapshot`. | +| Permission denied in Docker | Pre-create writable host directories and use your UID/GID mapping. | +| RRDP origin rejection | Inspect the publisher's notification/references; extra TLS roots do not bypass origin policy. | +| Daemon root locked | Stop the other instance normally; do not delete its lock file. | +| Unexpected counts or partial output | Inspect warnings and per-run status; use new output paths to avoid stale files. | + +See the [CLI reference](command-line-reference.md) for precise defaults, +timeouts, exit codes and recovery behavior. diff --git a/src/ccr/accumulator.rs b/src/ccr/accumulator.rs new file mode 100644 index 0000000..d69ef2b --- /dev/null +++ b/src/ccr/accumulator.rs @@ -0,0 +1,245 @@ +use std::collections::BTreeMap; + +use crate::ccr::build::{ + build_aspa_payload_state, build_roa_payload_state, build_router_key_state_from_runtime, + build_trust_anchor_state, +}; +use crate::ccr::encode::encode_manifest_state_payload_der; +use crate::ccr::hash::compute_state_hash; +use crate::ccr::manifest_location::select_manifest_signed_object_location_from_der; +use crate::ccr::model::{ + CcrDigestAlgorithm, ManifestInstance, ManifestState, RpkiCanonicalCacheRepresentation, +}; +use crate::model::common::BigUnsigned; +use crate::model::ta::TrustAnchor; +use crate::repository::storage::CcrManifestProjection; +use crate::validation::objects::{AspaAttestation, RouterKeyPayload, Vrp}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CcrManifestContribution { + pub manifest_rsync_uri: String, + pub hash: Vec, + pub size: u64, + pub aki: Vec, + pub manifest_number_be: Vec, + pub this_update: time::OffsetDateTime, + pub locations_der: Vec>, + pub subordinate_skis: Vec>, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct CcrAccumulatorMemoryStats { + pub trust_anchor_count: u64, + pub manifest_count: u64, + pub estimated_heap_bytes: u64, + pub string_bytes: u64, + pub string_capacity_bytes: u64, + pub vec_payload_bytes: u64, + pub vec_capacity_bytes: u64, + pub locations_der_count: u64, + pub subordinate_ski_count: u64, + pub btree_key_capacity_bytes: u64, + pub btree_entry_shallow_bytes: u64, +} + +impl CcrManifestContribution { + fn from_projection(projection: &CcrManifestProjection) -> Result { + let this_update = projection + .manifest_this_update + .parse() + .map_err(|e| format!("parse projection manifest_this_update failed: {e}"))?; + Ok(Self { + manifest_rsync_uri: projection.manifest_rsync_uri.clone(), + hash: projection.manifest_sha256.clone(), + size: projection.manifest_size, + aki: projection.manifest_ee_aki.clone(), + manifest_number_be: projection.manifest_number_be.clone(), + this_update, + locations_der: vec![select_manifest_signed_object_location_from_der( + &projection.manifest_rsync_uri, + &projection.manifest_sia_locations_der, + )?], + subordinate_skis: projection.subordinate_skis.clone(), + }) + } + + fn to_manifest_instance(&self) -> ManifestInstance { + ManifestInstance { + hash: self.hash.clone(), + size: self.size, + aki: self.aki.clone(), + manifest_number: BigUnsigned { + bytes_be: self.manifest_number_be.clone(), + }, + this_update: self.this_update, + locations: self.locations_der.clone(), + subordinates: self.subordinate_skis.clone(), + } + } + + fn add_memory_stats(&self, stats: &mut CcrAccumulatorMemoryStats) { + stats.string_bytes += self.manifest_rsync_uri.len() as u64; + stats.string_capacity_bytes += self.manifest_rsync_uri.capacity() as u64; + stats.estimated_heap_bytes += self.manifest_rsync_uri.capacity() as u64; + + add_vec_stats(&self.hash, stats); + add_vec_stats(&self.aki, stats); + add_vec_stats(&self.manifest_number_be, stats); + add_vec_of_vec_stats(&self.locations_der, stats); + add_vec_of_vec_stats(&self.subordinate_skis, stats); + stats.locations_der_count += self.locations_der.len() as u64; + stats.subordinate_ski_count += self.subordinate_skis.len() as u64; + } +} + +fn add_vec_stats(value: &Vec, stats: &mut CcrAccumulatorMemoryStats) { + stats.vec_payload_bytes += value.len() as u64; + stats.vec_capacity_bytes += value.capacity() as u64; + stats.estimated_heap_bytes += value.capacity() as u64; +} + +fn add_vec_of_vec_stats(values: &Vec>, stats: &mut CcrAccumulatorMemoryStats) { + let outer_capacity = values.capacity() * std::mem::size_of::>(); + stats.vec_payload_bytes += (values.len() * std::mem::size_of::>()) as u64; + stats.vec_capacity_bytes += outer_capacity as u64; + stats.estimated_heap_bytes += outer_capacity as u64; + for value in values { + add_vec_stats(value, stats); + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CcrAccumulator { + trust_anchors: Vec, + manifests_by_hash: BTreeMap, CcrManifestContribution>, + most_recent_update: time::OffsetDateTime, +} + +impl CcrAccumulator { + pub fn new(trust_anchors: Vec) -> Self { + Self { + trust_anchors, + manifests_by_hash: BTreeMap::new(), + most_recent_update: time::OffsetDateTime::UNIX_EPOCH, + } + } + + pub fn append_manifest_projection( + &mut self, + projection: &CcrManifestProjection, + ) -> Result<(), String> { + let contribution = CcrManifestContribution::from_projection(projection)?; + match self.manifests_by_hash.get(contribution.hash.as_slice()) { + Some(existing) if existing != &contribution => { + return Err(format!( + "duplicate manifest hash with conflicting content for URI: {}", + contribution.manifest_rsync_uri + )); + } + Some(_) => {} + None => { + self.manifests_by_hash + .insert(contribution.hash.clone(), contribution.clone()); + } + } + if contribution.this_update > self.most_recent_update { + self.most_recent_update = contribution.this_update; + } + Ok(()) + } + + pub fn finish( + &self, + produced_at: time::OffsetDateTime, + vrps: &[Vrp], + aspas: &[AspaAttestation], + router_keys: &[RouterKeyPayload], + ) -> Result { + let manifest_instances = self + .manifests_by_hash + .values() + .map(CcrManifestContribution::to_manifest_instance) + .collect::>(); + let manifest_payload_der = encode_manifest_state_payload_der(&manifest_instances) + .map_err(|e| format!("manifest state encoding failed: {e}"))?; + let manifest_state = ManifestState { + mis: manifest_instances, + most_recent_update: self.most_recent_update, + hash: compute_state_hash(&manifest_payload_der), + }; + let vrp_state = build_roa_payload_state(vrps).map_err(|e| e.to_string())?; + let aspa_state = build_aspa_payload_state(aspas).map_err(|e| e.to_string())?; + let ta_state = build_trust_anchor_state(&self.trust_anchors).map_err(|e| e.to_string())?; + let router_key_state = + build_router_key_state_from_runtime(router_keys).map_err(|e| e.to_string())?; + Ok(RpkiCanonicalCacheRepresentation { + version: 0, + hash_alg: CcrDigestAlgorithm::Sha256, + produced_at, + mfts: Some(manifest_state), + vrps: Some(vrp_state), + vaps: Some(aspa_state), + tas: Some(ta_state), + rks: Some(router_key_state), + }) + } + + pub fn manifest_count(&self) -> usize { + self.manifests_by_hash.len() + } + + pub fn memory_stats(&self) -> CcrAccumulatorMemoryStats { + let mut stats = CcrAccumulatorMemoryStats { + trust_anchor_count: self.trust_anchors.len() as u64, + manifest_count: self.manifests_by_hash.len() as u64, + ..CcrAccumulatorMemoryStats::default() + }; + stats.estimated_heap_bytes += + (self.trust_anchors.capacity() * std::mem::size_of::()) as u64; + for trust_anchor in &self.trust_anchors { + add_vec_stats(&trust_anchor.tal.raw, &mut stats); + add_vec_of_string_stats(&trust_anchor.tal.comments, &mut stats); + stats.vec_payload_bytes += + (trust_anchor.tal.ta_uris.len() * std::mem::size_of::()) as u64; + stats.vec_capacity_bytes += + (trust_anchor.tal.ta_uris.capacity() * std::mem::size_of::()) as u64; + stats.estimated_heap_bytes += + (trust_anchor.tal.ta_uris.capacity() * std::mem::size_of::()) as u64; + for uri in &trust_anchor.tal.ta_uris { + stats.string_bytes += uri.as_str().len() as u64; + stats.string_capacity_bytes += uri.as_str().len() as u64; + stats.estimated_heap_bytes += uri.as_str().len() as u64; + } + add_vec_stats(&trust_anchor.tal.subject_public_key_info_der, &mut stats); + add_vec_stats(&trust_anchor.ta_certificate.raw_der, &mut stats); + if let Some(uri) = &trust_anchor.resolved_ta_uri { + stats.string_bytes += uri.as_str().len() as u64; + stats.string_capacity_bytes += uri.as_str().len() as u64; + stats.estimated_heap_bytes += uri.as_str().len() as u64; + } + } + + stats.btree_entry_shallow_bytes = (self.manifests_by_hash.len() + * (std::mem::size_of::>() + std::mem::size_of::())) + as u64; + stats.estimated_heap_bytes += stats.btree_entry_shallow_bytes; + for (key, contribution) in &self.manifests_by_hash { + stats.btree_key_capacity_bytes += key.capacity() as u64; + stats.estimated_heap_bytes += key.capacity() as u64; + contribution.add_memory_stats(&mut stats); + } + stats + } +} + +fn add_vec_of_string_stats(values: &Vec, stats: &mut CcrAccumulatorMemoryStats) { + let outer_capacity = values.capacity() * std::mem::size_of::(); + stats.vec_payload_bytes += (values.len() * std::mem::size_of::()) as u64; + stats.vec_capacity_bytes += outer_capacity as u64; + stats.estimated_heap_bytes += outer_capacity as u64; + for value in values { + stats.string_bytes += value.len() as u64; + stats.string_capacity_bytes += value.capacity() as u64; + stats.estimated_heap_bytes += value.capacity() as u64; + } +} diff --git a/src/ccr/build.rs b/src/ccr/build.rs new file mode 100644 index 0000000..1d40ef9 --- /dev/null +++ b/src/ccr/build.rs @@ -0,0 +1,266 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use crate::ccr::encode::{ + encode_aspa_payload_state_payload_der, encode_roa_payload_state_payload_der, + encode_router_key_state_payload_der, encode_trust_anchor_state_payload_der, +}; +use crate::ccr::hash::compute_state_hash; +use crate::ccr::model::{ + AspaPayloadSet, AspaPayloadState, RoaPayloadSet, RoaPayloadState, RouterKey, RouterKeySet, + RouterKeyState, TrustAnchorState, +}; +use crate::model::roa::RoaAfi; +use crate::model::router_cert::BgpsecRouterCertificate; +use crate::model::ta::TrustAnchor; +use crate::validation::objects::{AspaAttestation, RouterKeyPayload, Vrp}; + +#[derive(Debug, thiserror::Error)] +pub enum CcrBuildError { + #[error("trust anchor set must not be empty")] + EmptyTrustAnchors, + #[error("trust anchor certificate missing SubjectKeyIdentifier")] + MissingTrustAnchorSki, + #[error("ROA payload state encoding failed: {0}")] + RoaEncode(String), + #[error("ASPA payload state encoding failed: {0}")] + AspaEncode(String), + #[error("TrustAnchor state encoding failed: {0}")] + TrustAnchorEncode(String), + #[error("router key state encoding failed: {0}")] + RouterKeyEncode(String), +} + +pub fn build_roa_payload_state(vrps: &[Vrp]) -> Result { + let mut grouped: BTreeMap> = BTreeMap::new(); + for vrp in vrps { + grouped + .entry(vrp.asn) + .or_default() + .insert(RoaPayloadKey::from_vrp(vrp)); + } + let rps = grouped + .into_iter() + .map(|(asn, entries)| { + let mut families: BTreeMap> = BTreeMap::new(); + for entry in entries { + families.entry(entry.afi).or_default().push(entry); + } + RoaPayloadSet { + as_id: asn, + ip_addr_blocks: families + .into_iter() + .map(|(afi, entries)| encode_roa_ip_address_family(afi, &entries)) + .collect(), + } + }) + .collect::>(); + let payload_der = encode_roa_payload_state_payload_der(&rps) + .map_err(|e| CcrBuildError::RoaEncode(e.to_string()))?; + Ok(RoaPayloadState { + rps, + hash: compute_state_hash(&payload_der), + }) +} + +pub fn build_aspa_payload_state( + attestations: &[AspaAttestation], +) -> Result { + let mut grouped: BTreeMap> = BTreeMap::new(); + for attestation in attestations { + grouped + .entry(attestation.customer_as_id) + .or_default() + .extend(attestation.provider_as_ids.iter().copied()); + } + let aps = grouped + .into_iter() + .map(|(customer_as_id, providers)| AspaPayloadSet { + customer_as_id, + providers: providers.into_iter().collect(), + }) + .collect::>(); + let payload_der = encode_aspa_payload_state_payload_der(&aps) + .map_err(|e| CcrBuildError::AspaEncode(e.to_string()))?; + Ok(AspaPayloadState { + aps, + hash: compute_state_hash(&payload_der), + }) +} + +pub fn build_trust_anchor_state( + trust_anchors: &[TrustAnchor], +) -> Result { + if trust_anchors.is_empty() { + return Err(CcrBuildError::EmptyTrustAnchors); + } + let mut skis = BTreeSet::new(); + for ta in trust_anchors { + let ski = ta + .ta_certificate + .rc_ca + .tbs + .extensions + .subject_key_identifier + .clone() + .ok_or(CcrBuildError::MissingTrustAnchorSki)?; + skis.insert(ski); + } + let skis = skis.into_iter().collect::>(); + let payload_der = encode_trust_anchor_state_payload_der(&skis) + .map_err(|e| CcrBuildError::TrustAnchorEncode(e.to_string()))?; + Ok(TrustAnchorState { + skis, + hash: compute_state_hash(&payload_der), + }) +} + +pub fn build_router_key_state( + router_certs: &[BgpsecRouterCertificate], +) -> Result { + let mut grouped: BTreeMap> = BTreeMap::new(); + for cert in router_certs { + let key = RouterKey { + ski: cert.subject_key_identifier.clone(), + spki_der: cert.spki_der.clone(), + }; + for asn in &cert.asns { + grouped.entry(*asn).or_default().insert(key.clone()); + } + } + build_router_key_sets(grouped) +} + +pub fn build_router_key_state_from_runtime( + router_keys: &[RouterKeyPayload], +) -> Result { + let mut grouped: BTreeMap> = BTreeMap::new(); + for key in router_keys { + grouped.entry(key.as_id).or_default().insert(RouterKey { + ski: key.ski.clone(), + spki_der: key.spki_der.clone(), + }); + } + build_router_key_sets(grouped) +} + +fn build_router_key_sets( + grouped: BTreeMap>, +) -> Result { + let rksets = grouped + .into_iter() + .map(|(as_id, router_keys)| RouterKeySet { + as_id, + router_keys: router_keys.into_iter().collect(), + }) + .collect::>(); + let payload_der = encode_router_key_state_payload_der(&rksets) + .map_err(|e| CcrBuildError::RouterKeyEncode(e.to_string()))?; + Ok(RouterKeyState { + rksets, + hash: compute_state_hash(&payload_der), + }) +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +struct RoaPayloadKey { + afi: u16, + addr: Vec, + prefix_len: u8, + max_length: u8, +} +impl RoaPayloadKey { + fn from_vrp(vrp: &Vrp) -> Self { + Self { + afi: match vrp.prefix.afi { + RoaAfi::Ipv4 => 1, + RoaAfi::Ipv6 => 2, + }, + addr: vrp.prefix.addr.to_vec(), + prefix_len: vrp.prefix.prefix_len as u8, + max_length: vrp.max_length as u8, + } + } +} +fn encode_roa_ip_address_family(afi: u16, entries: &[RoaPayloadKey]) -> Vec { + encode_sequence(&[ + encode_octet_string(&afi.to_be_bytes()), + encode_sequence( + &entries + .iter() + .map(encode_roa_ip_address) + .collect::>(), + ), + ]) +} +fn encode_roa_ip_address(entry: &RoaPayloadKey) -> Vec { + let (unused, content) = encode_prefix_bit_string(&entry.addr, entry.prefix_len); + let mut fields = vec![encode_bit_string(unused, &content)]; + if entry.max_length != entry.prefix_len { + fields.push(encode_integer_u8(entry.max_length)); + } + encode_sequence(&fields) +} +fn encode_prefix_bit_string(addr: &[u8], prefix_len: u8) -> (u8, Vec) { + if prefix_len == 0 { + return (0, Vec::new()); + } + let octets = (prefix_len as usize).div_ceil(8); + let mut content = addr[..octets].to_vec(); + let rem = prefix_len % 8; + let unused = if rem == 0 { 0 } else { 8 - rem }; + if unused > 0 { + *content.last_mut().expect("prefix has an octet") &= 0xff << unused; + } + (unused, content) +} +fn encode_integer_u8(value: u8) -> Vec { + encode_integer_bytes(vec![value]) +} +fn encode_integer_bytes(mut bytes: Vec) -> Vec { + if bytes.is_empty() { + bytes.push(0); + } + if bytes[0] & 0x80 != 0 { + bytes.insert(0, 0); + } + encode_tlv(0x02, bytes) +} +fn encode_bit_string(unused: u8, content: &[u8]) -> Vec { + let mut value = Vec::with_capacity(content.len() + 1); + value.push(unused); + value.extend_from_slice(content); + encode_tlv(0x03, value) +} +fn encode_octet_string(bytes: &[u8]) -> Vec { + encode_tlv(0x04, bytes.to_vec()) +} +fn encode_sequence(elements: &[Vec]) -> Vec { + encode_tlv( + 0x30, + elements + .iter() + .flat_map(|element| element.iter().copied()) + .collect(), + ) +} +fn encode_tlv(tag: u8, value: Vec) -> Vec { + let mut out = vec![tag]; + encode_length(value.len(), &mut out); + out.extend_from_slice(&value); + out +} +fn encode_length(len: usize, out: &mut Vec) { + if len < 0x80 { + out.push(len as u8); + return; + } + let mut bytes = Vec::new(); + let mut value = len; + while value > 0 { + bytes.push((value & 0xff) as u8); + value >>= 8; + } + bytes.reverse(); + out.push(0x80 | bytes.len() as u8); + out.extend_from_slice(&bytes); +} diff --git a/src/ccr/compare_view.rs b/src/ccr/compare_view.rs new file mode 100644 index 0000000..0c8d51c --- /dev/null +++ b/src/ccr/compare_view.rs @@ -0,0 +1,235 @@ +use std::collections::BTreeSet; +use std::io::Write; +use std::path::Path; + +use crate::ccr::{CcrContentInfo, extract_vrp_rows}; +use crate::validation::objects::{AspaAttestation, Vrp}; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct VrpCompareRow { + pub asn: String, + pub ip_prefix: String, + pub max_length: String, + pub trust_anchor: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct VapCompareRow { + pub customer_asn: String, + pub providers: String, + pub trust_anchor: String, +} + +fn normalize_asn(asn: u32) -> String { + format!("AS{asn}") +} + +pub fn canonical_vrp_prefix(prefix: &crate::model::roa::IpPrefix) -> String { + let mut addr = prefix.addr_bytes().to_vec(); + let total_bits = match prefix.afi { + crate::model::roa::RoaAfi::Ipv4 => 32usize, + crate::model::roa::RoaAfi::Ipv6 => 128usize, + }; + let keep = usize::from(prefix.prefix_len); + for bit in keep..total_bits { + let byte = bit / 8; + let offset = 7 - (bit % 8); + addr[byte] &= !(1u8 << offset); + } + match prefix.afi { + crate::model::roa::RoaAfi::Ipv4 => { + let ipv4 = std::net::Ipv4Addr::new(addr[0], addr[1], addr[2], addr[3]); + format!("{ipv4}/{}", prefix.prefix_len) + } + crate::model::roa::RoaAfi::Ipv6 => { + let mut bytes = [0u8; 16]; + bytes.copy_from_slice(&addr[..16]); + let ipv6 = std::net::Ipv6Addr::from(bytes); + format!("{ipv6}/{}", prefix.prefix_len) + } + } +} + +pub fn build_vrp_compare_rows(vrps: &[Vrp]) -> BTreeSet { + vrps.iter() + .map(|vrp| VrpCompareRow { + asn: normalize_asn(vrp.asn), + ip_prefix: canonical_vrp_prefix(&vrp.prefix), + max_length: vrp.max_length.to_string(), + trust_anchor: "unknown".to_string(), + }) + .collect() +} + +pub fn build_vap_compare_rows(aspas: &[AspaAttestation]) -> BTreeSet { + aspas + .iter() + .map(|aspa| { + let mut providers = aspa.provider_as_ids.to_vec(); + providers.sort_unstable(); + providers.dedup(); + VapCompareRow { + customer_asn: normalize_asn(aspa.customer_as_id), + providers: providers + .into_iter() + .map(normalize_asn) + .collect::>() + .join(";"), + trust_anchor: "unknown".to_string(), + } + }) + .collect() +} + +pub fn decode_ccr_compare_views( + content_info: &CcrContentInfo, +) -> Result<(BTreeSet, BTreeSet), String> { + let vrps = extract_vrp_rows(content_info) + .map_err(|e| format!("extract vrp rows from ccr failed: {e}"))? + .into_iter() + .map(|(asn, prefix, max_length)| VrpCompareRow { + asn: normalize_asn(asn), + ip_prefix: prefix, + max_length: max_length.to_string(), + trust_anchor: "unknown".to_string(), + }) + .collect::>(); + + let vaps = content_info + .content + .vaps + .as_ref() + .map(|state| { + state + .aps + .iter() + .map(|vap| VapCompareRow { + customer_asn: normalize_asn(vap.customer_as_id), + providers: vap + .providers + .iter() + .copied() + .map(normalize_asn) + .collect::>() + .join(";"), + trust_anchor: "unknown".to_string(), + }) + .collect::>() + }) + .unwrap_or_default(); + + Ok((vrps, vaps)) +} + +pub fn write_vrp_csv(path: &Path, rows: &BTreeSet) -> Result<(), String> { + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent) + .map_err(|e| format!("create parent dirs failed: {}: {e}", parent.display()))?; + } + let mut file = std::io::BufWriter::new( + std::fs::File::create(path) + .map_err(|e| format!("create file failed: {}: {e}", path.display()))?, + ); + writeln!(file, "ASN,IP Prefix,Max Length,Trust Anchor").map_err(|e| e.to_string())?; + for row in rows { + writeln!( + file, + "{},{},{},{}", + row.asn, row.ip_prefix, row.max_length, row.trust_anchor + ) + .map_err(|e| e.to_string())?; + } + Ok(()) +} + +pub fn write_vap_csv(path: &Path, rows: &BTreeSet) -> Result<(), String> { + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent) + .map_err(|e| format!("create parent dirs failed: {}: {e}", parent.display()))?; + } + let mut file = std::io::BufWriter::new( + std::fs::File::create(path) + .map_err(|e| format!("create file failed: {}: {e}", path.display()))?, + ); + writeln!(file, "Customer ASN,Providers,Trust Anchor").map_err(|e| e.to_string())?; + for row in rows { + writeln!( + file, + "{},{},{}", + row.customer_asn, row.providers, row.trust_anchor + ) + .map_err(|e| e.to_string())?; + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ccr::{ + CcrContentInfo, CcrDigestAlgorithm, RpkiCanonicalCacheRepresentation, + build_aspa_payload_state, build_roa_payload_state, + }; + use crate::model::roa::{IpPrefix, RoaAfi}; + + #[test] + fn build_vap_compare_rows_sorts_and_dedups_providers() { + let rows = build_vap_compare_rows(&[AspaAttestation { + customer_as_id: 64496, + provider_as_ids: vec![64498, 64497, 64498], + }]); + let row = rows.iter().next().expect("one row"); + assert_eq!(row.customer_asn, "AS64496"); + assert_eq!(row.providers, "AS64497;AS64498"); + assert_eq!(row.trust_anchor, "unknown"); + } + + #[test] + fn decode_ccr_compare_views_extracts_vrps_and_vaps() { + let vrps = build_roa_payload_state(&[Vrp { + asn: 64496, + prefix: IpPrefix { + afi: RoaAfi::Ipv4, + prefix_len: 24, + addr: [192, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + }, + max_length: 24, + }]) + .expect("build vrps"); + let vaps = build_aspa_payload_state(&[AspaAttestation { + customer_as_id: 64496, + provider_as_ids: vec![64497], + }]) + .expect("build vaps"); + let content = CcrContentInfo::new(RpkiCanonicalCacheRepresentation { + version: 0, + hash_alg: CcrDigestAlgorithm::Sha256, + produced_at: time::OffsetDateTime::now_utc(), + mfts: None, + vrps: Some(vrps), + vaps: Some(vaps), + tas: None, + rks: None, + }); + let (vrp_rows, vap_rows) = + decode_ccr_compare_views(&content).expect("decode compare views"); + assert_eq!(vrp_rows.len(), 1); + assert_eq!(vap_rows.len(), 1); + assert_eq!(vap_rows.iter().next().unwrap().providers, "AS64497"); + } + + #[test] + fn build_vrp_compare_rows_canonicalizes_ipv6_prefix_text() { + let rows = build_vrp_compare_rows(&[Vrp { + asn: 64496, + prefix: IpPrefix { + afi: RoaAfi::Ipv6, + prefix_len: 32, + addr: [0x20, 0x01, 0x0d, 0xb8, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + }, + max_length: 48, + }]); + let row = rows.iter().next().expect("row"); + assert_eq!(row.ip_prefix, "2001:db8::/32"); + } +} diff --git a/src/ccr/decode.rs b/src/ccr/decode.rs new file mode 100644 index 0000000..bc46bec --- /dev/null +++ b/src/ccr/decode.rs @@ -0,0 +1,541 @@ +use crate::ccr::model::{ + AspaPayloadSet, AspaPayloadState, CCR_VERSION_V0, CcrContentInfo, CcrDigestAlgorithm, + ManifestInstance, ManifestState, RoaPayloadSet, RoaPayloadState, RouterKey, RouterKeySet, + RouterKeyState, RpkiCanonicalCacheRepresentation, TrustAnchorState, +}; +use crate::model::common::{BigUnsigned, DerReader}; +use crate::model::oid::{OID_CT_RPKI_CCR, OID_CT_RPKI_CCR_RAW, OID_SHA256, OID_SHA256_RAW}; +use der_parser::der::parse_der_oid; + +#[derive(Debug, thiserror::Error)] +pub enum CcrDecodeError { + #[error("DER parse error: {0}")] + Parse(String), + + #[error("unexpected contentType OID: expected {expected}, got {actual}")] + UnexpectedContentType { + expected: &'static str, + actual: String, + }, + + #[error("unexpected digest algorithm OID: expected {expected}, got {actual}")] + UnexpectedDigestAlgorithm { + expected: &'static str, + actual: String, + }, + + #[error("CCR model validation failed after decode: {0}")] + Validate(String), +} + +pub fn decode_content_info(der: &[u8]) -> Result { + let mut top = DerReader::new(der); + let mut seq = top.take_sequence().map_err(CcrDecodeError::Parse)?; + if !top.is_empty() { + return Err(CcrDecodeError::Parse( + "trailing bytes after ContentInfo".into(), + )); + } + let content_type_raw = seq.take_tag(0x06).map_err(CcrDecodeError::Parse)?; + if content_type_raw != OID_CT_RPKI_CCR_RAW { + return Err(CcrDecodeError::UnexpectedContentType { + expected: OID_CT_RPKI_CCR, + actual: oid_string(content_type_raw)?, + }); + } + let inner = seq.take_tag(0xA0).map_err(CcrDecodeError::Parse)?; + if !seq.is_empty() { + return Err(CcrDecodeError::Parse( + "trailing fields in ContentInfo".into(), + )); + } + let content = decode_ccr(inner)?; + let ci = CcrContentInfo::new(content); + ci.validate().map_err(CcrDecodeError::Validate)?; + Ok(ci) +} + +pub fn decode_ccr(der: &[u8]) -> Result { + let mut top = DerReader::new(der); + let mut seq = top.take_sequence().map_err(CcrDecodeError::Parse)?; + if !top.is_empty() { + return Err(CcrDecodeError::Parse("trailing bytes after CCR".into())); + } + + let version = if !seq.is_empty() && seq.peek_tag().map_err(CcrDecodeError::Parse)? == 0xA0 { + let explicit = seq.take_tag(0xA0).map_err(CcrDecodeError::Parse)?; + let mut inner = DerReader::new(explicit); + let version = inner.take_uint_u64().map_err(CcrDecodeError::Parse)? as u32; + if !inner.is_empty() { + return Err(CcrDecodeError::Parse( + "trailing bytes inside CCR version EXPLICIT".into(), + )); + } + version + } else { + CCR_VERSION_V0 + }; + + let hash_alg = decode_digest_algorithm(seq.take_sequence().map_err(CcrDecodeError::Parse)?)?; + let produced_at = parse_generalized_time(seq.take_tag(0x18).map_err(CcrDecodeError::Parse)?)?; + + let mut mfts = None; + let mut vrps = None; + let mut vaps = None; + let mut tas = None; + let mut rks = None; + while !seq.is_empty() { + let tag = seq.peek_tag().map_err(CcrDecodeError::Parse)?; + let (tag_read, value) = seq.take_any().map_err(CcrDecodeError::Parse)?; + debug_assert_eq!(tag, tag_read); + match tag { + 0xA1 => mfts = Some(decode_manifest_state(value)?), + 0xA2 => vrps = Some(decode_roa_payload_state(value)?), + 0xA3 => vaps = Some(decode_aspa_payload_state(value)?), + 0xA4 => tas = Some(decode_trust_anchor_state(value)?), + 0xA5 => rks = Some(decode_router_key_state(value)?), + _ => { + return Err(CcrDecodeError::Parse(format!( + "unexpected CCR field tag 0x{tag:02X}" + ))); + } + } + } + + let ccr = RpkiCanonicalCacheRepresentation { + version, + hash_alg, + produced_at, + mfts, + vrps, + vaps, + tas, + rks, + }; + ccr.validate().map_err(CcrDecodeError::Validate)?; + Ok(ccr) +} + +fn decode_manifest_state(explicit_der: &[u8]) -> Result { + let mut outer = DerReader::new(explicit_der); + let mut seq = outer.take_sequence().map_err(CcrDecodeError::Parse)?; + if !outer.is_empty() { + return Err(CcrDecodeError::Parse( + "trailing bytes after ManifestState".into(), + )); + } + let mis_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?; + let mut mis_reader = DerReader::new(mis_der); + let mut mis = Vec::new(); + while !mis_reader.is_empty() { + let (_tag, full, _value) = mis_reader.take_any_full().map_err(CcrDecodeError::Parse)?; + mis.push(decode_manifest_instance(full)?); + } + let most_recent_update = + parse_generalized_time(seq.take_tag(0x18).map_err(CcrDecodeError::Parse)?)?; + let hash = seq + .take_octet_string() + .map_err(CcrDecodeError::Parse)? + .to_vec(); + if !seq.is_empty() { + return Err(CcrDecodeError::Parse( + "trailing fields in ManifestState".into(), + )); + } + Ok(ManifestState { + mis, + most_recent_update, + hash, + }) +} + +fn decode_manifest_instance(der: &[u8]) -> Result { + let mut top = DerReader::new(der); + let mut seq = top.take_sequence().map_err(CcrDecodeError::Parse)?; + if !top.is_empty() { + return Err(CcrDecodeError::Parse( + "trailing bytes after ManifestInstance".into(), + )); + } + let hash = seq + .take_octet_string() + .map_err(CcrDecodeError::Parse)? + .to_vec(); + let size = seq.take_uint_u64().map_err(CcrDecodeError::Parse)?; + let aki = seq + .take_octet_string() + .map_err(CcrDecodeError::Parse)? + .to_vec(); + let manifest_number = decode_big_unsigned(seq.take_tag(0x02).map_err(CcrDecodeError::Parse)?)?; + let this_update = parse_generalized_time(seq.take_tag(0x18).map_err(CcrDecodeError::Parse)?)?; + let locations_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?; + let mut locations_reader = DerReader::new(locations_der); + let mut locations = Vec::new(); + while !locations_reader.is_empty() { + let (_tag, full, _value) = locations_reader + .take_any_full() + .map_err(CcrDecodeError::Parse)?; + locations.push(full.to_vec()); + } + let subordinates = if !seq.is_empty() { + let subordinate_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?; + let mut reader = DerReader::new(subordinate_der); + let mut out = Vec::new(); + while !reader.is_empty() { + out.push( + reader + .take_octet_string() + .map_err(CcrDecodeError::Parse)? + .to_vec(), + ); + } + out + } else { + Vec::new() + }; + Ok(ManifestInstance { + hash, + size, + aki, + manifest_number, + this_update, + locations, + subordinates, + }) +} + +fn decode_roa_payload_state(explicit_der: &[u8]) -> Result { + let mut outer = DerReader::new(explicit_der); + let mut seq = outer.take_sequence().map_err(CcrDecodeError::Parse)?; + if !outer.is_empty() { + return Err(CcrDecodeError::Parse( + "trailing bytes after ROAPayloadState".into(), + )); + } + let payload_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?; + let mut reader = DerReader::new(payload_der); + let mut rps = Vec::new(); + while !reader.is_empty() { + let (_tag, full, _value) = reader.take_any_full().map_err(CcrDecodeError::Parse)?; + rps.push(decode_roa_payload_set(full)?); + } + let hash = seq + .take_octet_string() + .map_err(CcrDecodeError::Parse)? + .to_vec(); + Ok(RoaPayloadState { rps, hash }) +} + +fn decode_roa_payload_set(der: &[u8]) -> Result { + let mut top = DerReader::new(der); + let mut seq = top.take_sequence().map_err(CcrDecodeError::Parse)?; + if !top.is_empty() { + return Err(CcrDecodeError::Parse( + "trailing bytes after ROAPayloadSet".into(), + )); + } + let as_id = seq.take_uint_u64().map_err(CcrDecodeError::Parse)? as u32; + let blocks_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?; + let mut reader = DerReader::new(blocks_der); + let mut ip_addr_blocks = Vec::new(); + while !reader.is_empty() { + let (_tag, full, _value) = reader.take_any_full().map_err(CcrDecodeError::Parse)?; + ip_addr_blocks.push(full.to_vec()); + } + Ok(RoaPayloadSet { + as_id, + ip_addr_blocks, + }) +} + +fn decode_aspa_payload_state(explicit_der: &[u8]) -> Result { + let mut outer = DerReader::new(explicit_der); + let mut seq = outer.take_sequence().map_err(CcrDecodeError::Parse)?; + if !outer.is_empty() { + return Err(CcrDecodeError::Parse( + "trailing bytes after ASPAPayloadState".into(), + )); + } + let payload_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?; + let mut reader = DerReader::new(payload_der); + let mut aps = Vec::new(); + while !reader.is_empty() { + let (_tag, full, _value) = reader.take_any_full().map_err(CcrDecodeError::Parse)?; + aps.push(decode_aspa_payload_set(full)?); + } + let hash = seq + .take_octet_string() + .map_err(CcrDecodeError::Parse)? + .to_vec(); + Ok(AspaPayloadState { aps, hash }) +} + +fn decode_aspa_payload_set(der: &[u8]) -> Result { + let mut top = DerReader::new(der); + let mut seq = top.take_sequence().map_err(CcrDecodeError::Parse)?; + if !top.is_empty() { + return Err(CcrDecodeError::Parse( + "trailing bytes after ASPAPayloadSet".into(), + )); + } + let customer_as_id = seq.take_uint_u64().map_err(CcrDecodeError::Parse)? as u32; + let providers_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?; + let mut reader = DerReader::new(providers_der); + let mut providers = Vec::new(); + while !reader.is_empty() { + providers.push(reader.take_uint_u64().map_err(CcrDecodeError::Parse)? as u32); + } + Ok(AspaPayloadSet { + customer_as_id, + providers, + }) +} + +fn decode_trust_anchor_state(explicit_der: &[u8]) -> Result { + let mut outer = DerReader::new(explicit_der); + let mut seq = outer.take_sequence().map_err(CcrDecodeError::Parse)?; + if !outer.is_empty() { + return Err(CcrDecodeError::Parse( + "trailing bytes after TrustAnchorState".into(), + )); + } + let skis_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?; + let mut reader = DerReader::new(skis_der); + let mut skis = Vec::new(); + while !reader.is_empty() { + skis.push( + reader + .take_octet_string() + .map_err(CcrDecodeError::Parse)? + .to_vec(), + ); + } + let hash = seq + .take_octet_string() + .map_err(CcrDecodeError::Parse)? + .to_vec(); + Ok(TrustAnchorState { skis, hash }) +} + +fn decode_router_key_state(explicit_der: &[u8]) -> Result { + let mut outer = DerReader::new(explicit_der); + let mut seq = outer.take_sequence().map_err(CcrDecodeError::Parse)?; + if !outer.is_empty() { + return Err(CcrDecodeError::Parse( + "trailing bytes after RouterKeyState".into(), + )); + } + let sets_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?; + let mut reader = DerReader::new(sets_der); + let mut rksets = Vec::new(); + while !reader.is_empty() { + let (_tag, full, _value) = reader.take_any_full().map_err(CcrDecodeError::Parse)?; + rksets.push(decode_router_key_set(full)?); + } + let hash = seq + .take_octet_string() + .map_err(CcrDecodeError::Parse)? + .to_vec(); + Ok(RouterKeyState { rksets, hash }) +} + +fn decode_router_key_set(der: &[u8]) -> Result { + let mut top = DerReader::new(der); + let mut seq = top.take_sequence().map_err(CcrDecodeError::Parse)?; + if !top.is_empty() { + return Err(CcrDecodeError::Parse( + "trailing bytes after RouterKeySet".into(), + )); + } + let as_id = seq.take_uint_u64().map_err(CcrDecodeError::Parse)? as u32; + let keys_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?; + let mut reader = DerReader::new(keys_der); + let mut router_keys = Vec::new(); + while !reader.is_empty() { + let (_tag, full, _value) = reader.take_any_full().map_err(CcrDecodeError::Parse)?; + router_keys.push(decode_router_key(full)?); + } + Ok(RouterKeySet { as_id, router_keys }) +} + +fn decode_router_key(der: &[u8]) -> Result { + let mut top = DerReader::new(der); + let mut seq = top.take_sequence().map_err(CcrDecodeError::Parse)?; + if !top.is_empty() { + return Err(CcrDecodeError::Parse( + "trailing bytes after RouterKey".into(), + )); + } + let ski = seq + .take_octet_string() + .map_err(CcrDecodeError::Parse)? + .to_vec(); + let (_tag, full, _value) = seq.take_any_full().map_err(CcrDecodeError::Parse)?; + if !seq.is_empty() { + return Err(CcrDecodeError::Parse("trailing fields in RouterKey".into())); + } + Ok(RouterKey { + ski, + spki_der: full.to_vec(), + }) +} + +fn decode_digest_algorithm(mut seq: DerReader<'_>) -> Result { + let oid_raw = seq.take_tag(0x06).map_err(CcrDecodeError::Parse)?; + if oid_raw != OID_SHA256_RAW { + return Err(CcrDecodeError::UnexpectedDigestAlgorithm { + expected: OID_SHA256, + actual: oid_string(oid_raw)?, + }); + } + if !seq.is_empty() { + let tag = seq.peek_tag().map_err(CcrDecodeError::Parse)?; + if tag == 0x05 { + let null = seq.take_tag(0x05).map_err(CcrDecodeError::Parse)?; + if !null.is_empty() { + return Err(CcrDecodeError::Parse( + "AlgorithmIdentifier NULL parameters must be empty".into(), + )); + } + } + } + if !seq.is_empty() { + return Err(CcrDecodeError::Parse( + "trailing fields in DigestAlgorithmIdentifier".into(), + )); + } + Ok(CcrDigestAlgorithm::Sha256) +} + +fn oid_string(raw_body: &[u8]) -> Result { + let der = { + let mut out = Vec::with_capacity(raw_body.len() + 2); + out.push(0x06); + if raw_body.len() < 0x80 { + out.push(raw_body.len() as u8); + } else { + return Err(CcrDecodeError::Parse("OID too long".into())); + } + out.extend_from_slice(raw_body); + out + }; + let (_rem, oid) = parse_der_oid(&der).map_err(|e| CcrDecodeError::Parse(e.to_string()))?; + let oid = oid + .as_oid_val() + .map_err(|e| CcrDecodeError::Parse(e.to_string()))?; + Ok(oid.to_string()) +} + +fn parse_generalized_time(bytes: &[u8]) -> Result { + let s = std::str::from_utf8(bytes).map_err(|e| CcrDecodeError::Parse(e.to_string()))?; + if s.len() != 15 || !s.ends_with('Z') { + return Err(CcrDecodeError::Parse( + "GeneralizedTime must be YYYYMMDDHHMMSSZ".into(), + )); + } + let parse = |range: std::ops::Range| -> Result { + s[range] + .parse::() + .map_err(|e| CcrDecodeError::Parse(e.to_string())) + }; + let year = parse(0..4)? as i32; + let month = parse(4..6)? as u8; + let day = parse(6..8)? as u8; + let hour = parse(8..10)? as u8; + let minute = parse(10..12)? as u8; + let second = parse(12..14)? as u8; + let month = time::Month::try_from(month).map_err(|e| CcrDecodeError::Parse(e.to_string()))?; + let date = time::Date::from_calendar_date(year, month, day) + .map_err(|e| CcrDecodeError::Parse(e.to_string()))?; + let timev = time::Time::from_hms(hour, minute, second) + .map_err(|e| CcrDecodeError::Parse(e.to_string()))?; + Ok(time::PrimitiveDateTime::new(date, timev).assume_utc()) +} + +fn decode_big_unsigned(bytes: &[u8]) -> Result { + if bytes.is_empty() { + return Err(CcrDecodeError::Parse("INTEGER has empty content".into())); + } + if bytes[0] & 0x80 != 0 { + return Err(CcrDecodeError::Parse("INTEGER must be non-negative".into())); + } + if bytes.len() > 1 && bytes[0] == 0x00 && (bytes[1] & 0x80) == 0 { + return Err(CcrDecodeError::Parse( + "INTEGER not minimally encoded".into(), + )); + } + let bytes_be = if bytes.len() > 1 && bytes[0] == 0x00 { + bytes[1..].to_vec() + } else { + bytes.to_vec() + }; + Ok(BigUnsigned { bytes_be }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ccr::encode::{encode_content_info, encode_manifest_state_payload_der}; + use crate::ccr::hash::compute_state_hash; + use crate::ccr::manifest_location::encode_access_description_der; + use crate::ccr::model::{ + CcrContentInfo, CcrDigestAlgorithm, ManifestState, RpkiCanonicalCacheRepresentation, + }; + use crate::model::oid::{OID_AD_RPKI_NOTIFY, OID_AD_SIGNED_OBJECT}; + use crate::model::rc::AccessDescription; + + #[test] + fn generic_decoder_preserves_multiple_manifest_locations() { + let signed_object = encode_access_description_der(&AccessDescription { + access_method_oid: OID_AD_SIGNED_OBJECT.to_string(), + access_location: "rsync://example.test/repository/current.mft".to_string(), + }) + .expect("encode signedObject"); + let rpki_notify = encode_access_description_der(&AccessDescription { + access_method_oid: OID_AD_RPKI_NOTIFY.to_string(), + access_location: "https://rrdp.example.test/notification.xml".to_string(), + }) + .expect("encode rpkiNotify"); + let manifest = ManifestInstance { + hash: vec![0x11; 32], + size: 1024, + aki: vec![0x22; 20], + manifest_number: BigUnsigned { bytes_be: vec![1] }, + this_update: time::OffsetDateTime::parse( + "2026-07-20T00:00:00Z", + &time::format_description::well_known::Rfc3339, + ) + .expect("parse time"), + locations: vec![signed_object, rpki_notify], + subordinates: Vec::new(), + }; + let manifest_payload = encode_manifest_state_payload_der(std::slice::from_ref(&manifest)) + .expect("encode manifest payload"); + let content = CcrContentInfo::new(RpkiCanonicalCacheRepresentation { + version: 0, + hash_alg: CcrDigestAlgorithm::Sha256, + produced_at: time::OffsetDateTime::parse( + "2026-07-20T00:00:00Z", + &time::format_description::well_known::Rfc3339, + ) + .expect("parse time"), + mfts: Some(ManifestState { + mis: vec![manifest], + most_recent_update: time::OffsetDateTime::parse( + "2026-07-20T00:00:00Z", + &time::format_description::well_known::Rfc3339, + ) + .expect("parse time"), + hash: compute_state_hash(&manifest_payload), + }), + vrps: None, + vaps: None, + tas: None, + rks: None, + }); + + let encoded = encode_content_info(&content).expect("encode CCR"); + let decoded = decode_content_info(&encoded).expect("decode CCR"); + assert_eq!(decoded.content.mfts.unwrap().mis[0].locations.len(), 2); + } +} diff --git a/src/ccr/encode.rs b/src/ccr/encode.rs new file mode 100644 index 0000000..a5c9e85 --- /dev/null +++ b/src/ccr/encode.rs @@ -0,0 +1,303 @@ +use crate::ccr::model::{ + AspaPayloadSet, AspaPayloadState, CCR_VERSION_V0, CcrContentInfo, CcrDigestAlgorithm, + ManifestInstance, ManifestState, RoaPayloadSet, RoaPayloadState, RouterKey, RouterKeySet, + RouterKeyState, RpkiCanonicalCacheRepresentation, TrustAnchorState, +}; +use crate::model::common::BigUnsigned; +use crate::model::oid::{OID_CT_RPKI_CCR_RAW, OID_SHA256_RAW}; + +#[derive(Debug, thiserror::Error)] +pub enum CcrEncodeError { + #[error("CCR model validation failed: {0}")] + Validate(String), + + #[error("GeneralizedTime formatting failed: {0}")] + ProducedAtFormat(String), +} + +pub fn encode_content_info(content_info: &CcrContentInfo) -> Result, CcrEncodeError> { + content_info.validate().map_err(CcrEncodeError::Validate)?; + let content_der = encode_ccr(&content_info.content)?; + Ok(encode_sequence(&[ + encode_oid(OID_CT_RPKI_CCR_RAW), + encode_explicit(0, &content_der), + ])) +} + +pub fn encode_ccr(ccr: &RpkiCanonicalCacheRepresentation) -> Result, CcrEncodeError> { + ccr.validate().map_err(CcrEncodeError::Validate)?; + let mut fields = Vec::new(); + if ccr.version != CCR_VERSION_V0 { + fields.push(encode_explicit(0, &encode_integer_u32(ccr.version))); + } + fields.push(encode_digest_algorithm(&ccr.hash_alg)); + fields.push(encode_generalized_time(ccr.produced_at)?); + if let Some(mfts) = &ccr.mfts { + fields.push(encode_explicit(1, &encode_manifest_state(mfts)?)); + } + if let Some(vrps) = &ccr.vrps { + fields.push(encode_explicit(2, &encode_roa_payload_state(vrps)?)); + } + if let Some(vaps) = &ccr.vaps { + fields.push(encode_explicit(3, &encode_aspa_payload_state(vaps)?)); + } + if let Some(tas) = &ccr.tas { + fields.push(encode_explicit(4, &encode_trust_anchor_state(tas)?)); + } + if let Some(rks) = &ccr.rks { + fields.push(encode_explicit(5, &encode_router_key_state(rks)?)); + } + Ok(encode_sequence(&fields)) +} + +pub fn encode_manifest_state(state: &ManifestState) -> Result, CcrEncodeError> { + state.validate().map_err(CcrEncodeError::Validate)?; + let mis = encode_manifest_state_payload_der(&state.mis)?; + Ok(encode_sequence(&[ + mis, + encode_generalized_time(state.most_recent_update)?, + encode_octet_string(&state.hash), + ])) +} + +pub fn encode_manifest_state_payload_der( + instances: &[ManifestInstance], +) -> Result, CcrEncodeError> { + Ok(encode_sequence( + &instances + .iter() + .map(encode_manifest_instance) + .collect::, _>>()?, + )) +} + +fn encode_manifest_instance(instance: &ManifestInstance) -> Result, CcrEncodeError> { + instance.validate().map_err(CcrEncodeError::Validate)?; + let mut fields = vec![ + encode_octet_string(&instance.hash), + encode_integer_u64(instance.size), + encode_octet_string(&instance.aki), + encode_integer_bigunsigned(&instance.manifest_number), + encode_generalized_time(instance.this_update)?, + encode_sequence(&instance.locations), + ]; + if !instance.subordinates.is_empty() { + fields.push(encode_sequence( + &instance + .subordinates + .iter() + .map(|ski| encode_octet_string(ski)) + .collect::>(), + )); + } + Ok(encode_sequence(&fields)) +} + +pub fn encode_roa_payload_state(state: &RoaPayloadState) -> Result, CcrEncodeError> { + state.validate().map_err(CcrEncodeError::Validate)?; + let rps = encode_roa_payload_state_payload_der(&state.rps)?; + Ok(encode_sequence(&[rps, encode_octet_string(&state.hash)])) +} + +pub fn encode_roa_payload_state_payload_der( + sets: &[RoaPayloadSet], +) -> Result, CcrEncodeError> { + Ok(encode_sequence( + &sets + .iter() + .map(encode_roa_payload_set) + .collect::, _>>()?, + )) +} + +fn encode_roa_payload_set(set: &RoaPayloadSet) -> Result, CcrEncodeError> { + set.validate().map_err(CcrEncodeError::Validate)?; + Ok(encode_sequence(&[ + encode_integer_u32(set.as_id), + encode_sequence(&set.ip_addr_blocks), + ])) +} + +pub fn encode_aspa_payload_state(state: &AspaPayloadState) -> Result, CcrEncodeError> { + state.validate().map_err(CcrEncodeError::Validate)?; + let aps = encode_aspa_payload_state_payload_der(&state.aps)?; + Ok(encode_sequence(&[aps, encode_octet_string(&state.hash)])) +} + +pub fn encode_aspa_payload_state_payload_der( + sets: &[AspaPayloadSet], +) -> Result, CcrEncodeError> { + Ok(encode_sequence( + &sets + .iter() + .map(encode_aspa_payload_set) + .collect::, _>>()?, + )) +} + +fn encode_aspa_payload_set(set: &AspaPayloadSet) -> Result, CcrEncodeError> { + set.validate().map_err(CcrEncodeError::Validate)?; + Ok(encode_sequence(&[ + encode_integer_u32(set.customer_as_id), + encode_sequence( + &set.providers + .iter() + .map(|provider| encode_integer_u32(*provider)) + .collect::>(), + ), + ])) +} + +pub fn encode_trust_anchor_state(state: &TrustAnchorState) -> Result, CcrEncodeError> { + state.validate().map_err(CcrEncodeError::Validate)?; + let skis = encode_trust_anchor_state_payload_der(&state.skis)?; + Ok(encode_sequence(&[skis, encode_octet_string(&state.hash)])) +} + +pub fn encode_trust_anchor_state_payload_der(skis: &[Vec]) -> Result, CcrEncodeError> { + Ok(encode_sequence( + &skis + .iter() + .map(|ski| encode_octet_string(ski)) + .collect::>(), + )) +} + +pub fn encode_router_key_state(state: &RouterKeyState) -> Result, CcrEncodeError> { + state.validate().map_err(CcrEncodeError::Validate)?; + let rksets = encode_router_key_state_payload_der(&state.rksets)?; + Ok(encode_sequence(&[rksets, encode_octet_string(&state.hash)])) +} + +pub fn encode_router_key_state_payload_der( + sets: &[RouterKeySet], +) -> Result, CcrEncodeError> { + Ok(encode_sequence( + &sets + .iter() + .map(encode_router_key_set) + .collect::, _>>()?, + )) +} + +fn encode_router_key_set(set: &RouterKeySet) -> Result, CcrEncodeError> { + set.validate().map_err(CcrEncodeError::Validate)?; + Ok(encode_sequence(&[ + encode_integer_u32(set.as_id), + encode_sequence( + &set.router_keys + .iter() + .map(encode_router_key) + .collect::, _>>()?, + ), + ])) +} + +fn encode_router_key(key: &RouterKey) -> Result, CcrEncodeError> { + key.validate().map_err(CcrEncodeError::Validate)?; + Ok(encode_sequence(&[ + encode_octet_string(&key.ski), + key.spki_der.clone(), + ])) +} + +fn encode_digest_algorithm(alg: &CcrDigestAlgorithm) -> Vec { + match alg { + CcrDigestAlgorithm::Sha256 => encode_sequence(&[encode_oid(OID_SHA256_RAW)]), + } +} + +fn encode_generalized_time(t: time::OffsetDateTime) -> Result, CcrEncodeError> { + let t = t.to_offset(time::UtcOffset::UTC); + let s = format!( + "{:04}{:02}{:02}{:02}{:02}{:02}Z", + t.year(), + u8::from(t.month()), + t.day(), + t.hour(), + t.minute(), + t.second() + ); + Ok(encode_tlv(0x18, s.into_bytes())) +} + +fn encode_integer_u32(v: u32) -> Vec { + encode_integer_bytes(unsigned_integer_bytes(v as u64)) +} + +fn encode_integer_u64(v: u64) -> Vec { + encode_integer_bytes(unsigned_integer_bytes(v)) +} + +fn encode_integer_bigunsigned(v: &BigUnsigned) -> Vec { + encode_integer_bytes(v.bytes_be.clone()) +} + +fn encode_integer_bytes(mut bytes: Vec) -> Vec { + if bytes.is_empty() { + bytes.push(0); + } + if bytes[0] & 0x80 != 0 { + bytes.insert(0, 0); + } + encode_tlv(0x02, bytes) +} + +fn unsigned_integer_bytes(v: u64) -> Vec { + if v == 0 { + return vec![0]; + } + let mut out = Vec::new(); + let mut n = v; + while n > 0 { + out.push((n & 0xFF) as u8); + n >>= 8; + } + out.reverse(); + out +} + +fn encode_oid(raw_body: &[u8]) -> Vec { + encode_tlv(0x06, raw_body.to_vec()) +} + +fn encode_octet_string(bytes: &[u8]) -> Vec { + encode_tlv(0x04, bytes.to_vec()) +} + +fn encode_explicit(tag_number: u8, inner_der: &[u8]) -> Vec { + encode_tlv(0xA0 + tag_number, inner_der.to_vec()) +} + +fn encode_sequence(elements: &[Vec]) -> Vec { + let total_len: usize = elements.iter().map(Vec::len).sum(); + let mut buf = Vec::with_capacity(total_len); + for element in elements { + buf.extend_from_slice(element); + } + encode_tlv(0x30, buf) +} + +fn encode_tlv(tag: u8, value: Vec) -> Vec { + let mut out = Vec::with_capacity(1 + 9 + value.len()); + out.push(tag); + encode_length(value.len(), &mut out); + out.extend_from_slice(&value); + out +} + +fn encode_length(len: usize, out: &mut Vec) { + if len < 0x80 { + out.push(len as u8); + return; + } + let mut bytes = Vec::new(); + let mut value = len; + while value > 0 { + bytes.push((value & 0xFF) as u8); + value >>= 8; + } + bytes.reverse(); + out.push(0x80 | (bytes.len() as u8)); + out.extend_from_slice(&bytes); +} diff --git a/src/ccr/export.rs b/src/ccr/export.rs new file mode 100644 index 0000000..24a4b10 --- /dev/null +++ b/src/ccr/export.rs @@ -0,0 +1,25 @@ +use crate::ccr::encode::{CcrEncodeError, encode_content_info}; +use crate::ccr::model::{CcrContentInfo, RpkiCanonicalCacheRepresentation}; +use std::path::Path; + +#[derive(Debug, thiserror::Error)] +pub enum CcrExportError { + #[error("encode CCR failed: {0}")] + Encode(#[from] CcrEncodeError), + #[error("write CCR file failed: {0}: {1}")] + Write(String, String), +} + +pub fn write_ccr_file( + path: &Path, + ccr: &RpkiCanonicalCacheRepresentation, +) -> Result<(), CcrExportError> { + let der = encode_content_info(&CcrContentInfo::new(ccr.clone()))?; + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent).map_err(|error| { + CcrExportError::Write(path.display().to_string(), error.to_string()) + })?; + } + std::fs::write(path, der) + .map_err(|error| CcrExportError::Write(path.display().to_string(), error.to_string())) +} diff --git a/src/ccr/hash.rs b/src/ccr/hash.rs new file mode 100644 index 0000000..23b203a --- /dev/null +++ b/src/ccr/hash.rs @@ -0,0 +1,9 @@ +use sha2::Digest; + +pub fn compute_state_hash(payload_der: &[u8]) -> Vec { + sha2::Sha256::digest(payload_der).to_vec() +} + +pub fn verify_state_hash(expected: &[u8], payload_der: &[u8]) -> bool { + compute_state_hash(payload_der).as_slice() == expected +} diff --git a/src/ccr/manifest_location.rs b/src/ccr/manifest_location.rs new file mode 100644 index 0000000..791293f --- /dev/null +++ b/src/ccr/manifest_location.rs @@ -0,0 +1,425 @@ +use crate::model::common::DerReader; +use crate::model::oid::OID_AD_SIGNED_OBJECT; +use crate::model::rc::AccessDescription; + +pub(crate) fn select_manifest_signed_object_location( + manifest_rsync_uri: &str, + access_descriptions: &[AccessDescription], +) -> Result, String> { + let matching = access_descriptions + .iter() + .filter(|access_description| { + access_description.access_method_oid == OID_AD_SIGNED_OBJECT + && access_description.access_location == manifest_rsync_uri + }) + .collect::>(); + let access_description = expect_single_matching_location( + manifest_rsync_uri, + matching.len(), + "parsed Manifest EE SIA", + )?; + encode_access_description_der(matching[access_description]) +} + +pub(crate) fn select_manifest_signed_object_location_from_der( + manifest_rsync_uri: &str, + locations_der: &[Vec], +) -> Result, String> { + let matching = locations_der + .iter() + .filter_map( + |location_der| match decode_access_description_der(location_der) { + Ok(access_description) + if access_description.access_method_oid == OID_AD_SIGNED_OBJECT + && access_description.access_location == manifest_rsync_uri => + { + Some(Ok(location_der.clone())) + } + Ok(_) => None, + Err(detail) => Some(Err(detail)), + }, + ) + .collect::, _>>()?; + let index = expect_single_matching_location( + manifest_rsync_uri, + matching.len(), + "CCR manifest projection", + )?; + Ok(matching[index].clone()) +} + +pub(crate) fn encode_access_description_der( + access_description: &AccessDescription, +) -> Result, String> { + let oid = encode_oid_der(&access_description.access_method_oid)?; + let uri = encode_tlv(0x86, access_description.access_location.as_bytes().to_vec()); + Ok(encode_sequence(&[oid, uri])) +} + +fn expect_single_matching_location( + manifest_rsync_uri: &str, + matching_count: usize, + source: &str, +) -> Result { + if matching_count == 1 { + return Ok(0); + } + Err(format!( + "{source} contains {matching_count} id-ad-signedObject locations matching manifest URI {manifest_rsync_uri}; expected exactly one" + )) +} + +fn decode_access_description_der(der: &[u8]) -> Result { + let mut top = DerReader::new(der); + let mut sequence = top.take_sequence()?; + if !top.is_empty() { + return Err("trailing bytes after AccessDescription".to_string()); + } + let access_method_oid = decode_oid_der(sequence.take_tag(0x06)?)?; + let access_location = std::str::from_utf8(sequence.take_tag(0x86)?) + .map_err(|error| format!("AccessDescription URI is not UTF-8: {error}"))? + .to_string(); + if !sequence.is_empty() { + return Err("trailing fields in AccessDescription".to_string()); + } + Ok(AccessDescription { + access_method_oid, + access_location, + }) +} + +fn decode_oid_der(value: &[u8]) -> Result { + let mut offset = 0usize; + let first = decode_base128(value, &mut offset)?; + let (first_arc, second_arc) = match first { + 0..=39 => (0, first), + 40..=79 => (1, first - 40), + value => (2, value - 80), + }; + let mut arcs = vec![first_arc, second_arc]; + while offset < value.len() { + arcs.push(decode_base128(value, &mut offset)?); + } + Ok(arcs + .into_iter() + .map(|arc| arc.to_string()) + .collect::>() + .join(".")) +} + +fn decode_base128(value: &[u8], offset: &mut usize) -> Result { + let first = *value + .get(*offset) + .ok_or_else(|| "truncated OBJECT IDENTIFIER".to_string())?; + if first == 0x80 { + return Err("non-minimal OBJECT IDENTIFIER base-128 encoding".to_string()); + } + let mut out = 0u64; + loop { + let byte = *value + .get(*offset) + .ok_or_else(|| "truncated OBJECT IDENTIFIER".to_string())?; + *offset += 1; + out = out + .checked_shl(7) + .ok_or_else(|| "OBJECT IDENTIFIER arc overflows u64".to_string())? + .checked_add((byte & 0x7f) as u64) + .ok_or_else(|| "OBJECT IDENTIFIER arc overflows u64".to_string())?; + if byte & 0x80 == 0 { + return Ok(out); + } + } +} + +fn encode_oid_der(oid: &str) -> Result, String> { + let arcs = oid + .split('.') + .map(|part| { + part.parse::() + .map_err(|_| format!("unsupported accessMethod OID: {oid}")) + }) + .collect::, _>>()?; + if arcs.len() < 2 || arcs[0] > 2 || (arcs[0] < 2 && arcs[1] >= 40) { + return Err(format!("unsupported accessMethod OID: {oid}")); + } + let mut body = Vec::new(); + encode_base128( + arcs[0] + .checked_mul(40) + .and_then(|value| value.checked_add(arcs[1])) + .ok_or_else(|| format!("unsupported accessMethod OID: {oid}"))?, + &mut body, + ); + for arc in &arcs[2..] { + encode_base128(*arc, &mut body); + } + Ok(encode_tlv(0x06, body)) +} + +fn encode_base128(mut value: u64, out: &mut Vec) { + let mut encoded = vec![(value & 0x7f) as u8]; + value >>= 7; + while value > 0 { + encoded.push(((value & 0x7f) as u8) | 0x80); + value >>= 7; + } + encoded.reverse(); + out.extend_from_slice(&encoded); +} + +fn encode_sequence(elements: &[Vec]) -> Vec { + let total_len = elements.iter().map(Vec::len).sum(); + let mut value = Vec::with_capacity(total_len); + for element in elements { + value.extend_from_slice(element); + } + encode_tlv(0x30, value) +} + +fn encode_tlv(tag: u8, value: Vec) -> Vec { + let mut out = Vec::with_capacity(1 + 9 + value.len()); + out.push(tag); + encode_length(value.len(), &mut out); + out.extend_from_slice(&value); + out +} + +fn encode_length(len: usize, out: &mut Vec) { + if len < 0x80 { + out.push(len as u8); + return; + } + let mut bytes = Vec::new(); + let mut value = len; + while value > 0 { + bytes.push((value & 0xff) as u8); + value >>= 8; + } + bytes.reverse(); + out.push(0x80 | bytes.len() as u8); + out.extend_from_slice(&bytes); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::model::oid::OID_AD_RPKI_NOTIFY; + + const MANIFEST_URI: &str = "rsync://example.test/repo/manifest.mft"; + + fn access_description(access_method_oid: &str, access_location: &str) -> AccessDescription { + AccessDescription { + access_method_oid: access_method_oid.to_string(), + access_location: access_location.to_string(), + } + } + + #[test] + fn selects_matching_signed_object_and_excludes_rpki_notify() { + let signed_object = access_description(OID_AD_SIGNED_OBJECT, MANIFEST_URI); + let selected = select_manifest_signed_object_location( + MANIFEST_URI, + &[ + signed_object.clone(), + access_description( + OID_AD_RPKI_NOTIFY, + "https://rrdp.example.test/notification.xml", + ), + ], + ) + .expect("select signed object"); + + assert_eq!( + selected, + encode_access_description_der(&signed_object).unwrap() + ); + } + + #[test] + fn selects_only_the_signed_object_matching_manifest_uri() { + let expected = access_description(OID_AD_SIGNED_OBJECT, MANIFEST_URI); + let selected = select_manifest_signed_object_location( + MANIFEST_URI, + &[ + access_description( + OID_AD_SIGNED_OBJECT, + "https://backup.example.test/manifest.mft", + ), + expected.clone(), + ], + ) + .expect("select matching signed object"); + + assert_eq!(selected, encode_access_description_der(&expected).unwrap()); + } + + #[test] + fn rejects_missing_or_duplicate_matching_signed_object() { + let missing = select_manifest_signed_object_location( + MANIFEST_URI, + &[access_description( + OID_AD_RPKI_NOTIFY, + "https://rrdp.example.test/notification.xml", + )], + ) + .expect_err("missing signed object must fail"); + assert!(missing.contains("contains 0"), "{missing}"); + + let duplicate = select_manifest_signed_object_location( + MANIFEST_URI, + &[ + access_description(OID_AD_SIGNED_OBJECT, MANIFEST_URI), + access_description(OID_AD_SIGNED_OBJECT, MANIFEST_URI), + ], + ) + .expect_err("duplicate signed object must fail"); + assert!(duplicate.contains("contains 2"), "{duplicate}"); + } + + #[test] + fn selects_matching_signed_object_from_historical_projection() { + let signed_object = access_description(OID_AD_SIGNED_OBJECT, MANIFEST_URI); + let signed_object_der = encode_access_description_der(&signed_object).unwrap(); + let notify_der = encode_access_description_der(&access_description( + OID_AD_RPKI_NOTIFY, + "https://rrdp.example.test/notification.xml", + )) + .unwrap(); + + let selected = select_manifest_signed_object_location_from_der( + MANIFEST_URI, + &[notify_der, signed_object_der.clone()], + ) + .expect("select historical signed object"); + assert_eq!(selected, signed_object_der); + } + + #[test] + fn rejects_malformed_historical_projection() { + let error = select_manifest_signed_object_location_from_der( + MANIFEST_URI, + &[vec![0x30, 0x01, 0x06]], + ) + .expect_err("malformed historical projection must fail"); + assert!(error.contains("truncated DER"), "{error}"); + } + + #[test] + fn access_description_der_codec_covers_long_and_invalid_forms() { + let long = access_description( + "2.999.200.1", + &format!("rsync://example.test/repo/{}", "x".repeat(160)), + ); + let encoded = encode_access_description_der(&long).expect("encode long access description"); + assert_eq!( + decode_access_description_der(&encoded).expect("decode long access description"), + long + ); + + let mut trailing = encoded.clone(); + trailing.push(0); + assert!( + decode_access_description_der(&trailing) + .expect_err("trailing bytes must fail") + .contains("trailing bytes") + ); + + let oid = encode_oid_der(OID_AD_SIGNED_OBJECT).expect("encode signedObject OID"); + let uri = encode_tlv(0x86, MANIFEST_URI.as_bytes().to_vec()); + assert!( + decode_access_description_der(&encode_sequence(&[ + oid.clone(), + uri.clone(), + encode_tlv(0x05, Vec::new()), + ])) + .expect_err("trailing field must fail") + .contains("trailing fields") + ); + assert!( + decode_access_description_der(&encode_sequence(&[oid, encode_tlv(0x86, vec![0xff]),])) + .expect_err("non-UTF8 URI must fail") + .contains("not UTF-8") + ); + assert!( + decode_access_description_der(&encode_sequence(&[ + encode_tlv(0x06, vec![0x80, 0x00]), + uri.clone(), + ])) + .expect_err("non-minimal OID must fail") + .contains("non-minimal") + ); + assert!( + decode_access_description_der(&encode_sequence(&[encode_tlv(0x06, vec![0x81]), uri,])) + .expect_err("truncated OID must fail") + .contains("truncated OBJECT IDENTIFIER") + ); + assert!( + encode_access_description_der(&access_description("3.1", MANIFEST_URI)) + .expect_err("invalid OID must fail") + .contains("unsupported accessMethod OID") + ); + } + + #[test] + fn access_description_der_codec_reports_malformed_selector_inputs() { + let malformed_sequence = decode_access_description_der(&[0x31, 0x00]) + .expect_err("non-sequence AccessDescription must fail"); + assert!(!malformed_sequence.is_empty()); + + let missing_method = decode_access_description_der(&encode_sequence(&[encode_tlv( + 0x86, + MANIFEST_URI.as_bytes().to_vec(), + )])) + .expect_err("missing accessMethod must fail"); + assert!(!missing_method.is_empty()); + + let oid = encode_oid_der(OID_AD_SIGNED_OBJECT).expect("encode signedObject OID"); + let missing_location = + decode_access_description_der(&encode_sequence(std::slice::from_ref(&oid))) + .expect_err("missing accessLocation must fail"); + assert!(!missing_location.is_empty()); + + let wrong_location_tag = decode_access_description_der(&encode_sequence(&[ + oid.clone(), + encode_tlv(0x04, MANIFEST_URI.as_bytes().to_vec()), + ])) + .expect_err("wrong accessLocation tag must fail"); + assert!(!wrong_location_tag.is_empty()); + + let empty_oid = decode_access_description_der(&encode_sequence(&[ + encode_tlv(0x06, Vec::new()), + encode_tlv(0x86, MANIFEST_URI.as_bytes().to_vec()), + ])) + .expect_err("empty OID must fail"); + assert!(!empty_oid.is_empty()); + + let first_arc_zero = decode_access_description_der(&encode_sequence(&[ + encode_tlv(0x06, vec![0x01, 0x02]), + encode_tlv(0x86, MANIFEST_URI.as_bytes().to_vec()), + ])) + .expect("decode first OID arc zero"); + assert_eq!(first_arc_zero.access_method_oid, "0.1.2"); + assert_eq!(first_arc_zero.access_location, MANIFEST_URI); + + let non_numeric = + encode_access_description_der(&access_description("no.such.oid", MANIFEST_URI)) + .expect_err("non-numeric OID must fail"); + assert!(!non_numeric.is_empty()); + + let too_short = encode_access_description_der(&access_description("1", MANIFEST_URI)) + .expect_err("OID without second arc must fail"); + assert!(!too_short.is_empty()); + + let invalid_second_arc = + encode_access_description_der(&access_description("1.40", MANIFEST_URI)) + .expect_err("invalid second OID arc must fail"); + assert!(!invalid_second_arc.is_empty()); + + let overflowing_first_subidentifier = encode_access_description_der(&access_description( + "2.18446744073709551615", + MANIFEST_URI, + )) + .expect_err("overflowing first OID subidentifier must fail"); + assert!(!overflowing_first_subidentifier.is_empty()); + } +} diff --git a/src/ccr/mod.rs b/src/ccr/mod.rs new file mode 100644 index 0000000..a384ebc --- /dev/null +++ b/src/ccr/mod.rs @@ -0,0 +1,34 @@ +pub mod accumulator; +pub mod build; +pub mod compare_view; +pub mod decode; +pub mod encode; +pub mod export; +pub mod hash; +pub(crate) mod manifest_location; +pub mod model; +pub mod verify; + +pub use accumulator::{CcrAccumulator, CcrManifestContribution}; +pub use build::{ + CcrBuildError, build_aspa_payload_state, build_roa_payload_state, + build_router_key_state_from_runtime, build_trust_anchor_state, +}; +pub use compare_view::{ + VapCompareRow, VrpCompareRow, build_vap_compare_rows, build_vrp_compare_rows, + canonical_vrp_prefix, decode_ccr_compare_views, write_vap_csv, write_vrp_csv, +}; +pub use decode::{CcrDecodeError, decode_content_info}; +pub use encode::{CcrEncodeError, encode_content_info}; +pub use export::{CcrExportError, write_ccr_file}; +pub use hash::{compute_state_hash, verify_state_hash}; +pub use model::{ + AspaPayloadSet, AspaPayloadState, CcrContentInfo, CcrDigestAlgorithm, ManifestInstance, + ManifestState, RoaPayloadSet, RoaPayloadState, RouterKey, RouterKeySet, RouterKeyState, + RpkiCanonicalCacheRepresentation, TrustAnchorState, +}; +pub use verify::{ + CcrVerifyError, CcrVerifySummary, extract_vrp_rows, verify_against_report_json_path, + verify_content_info, verify_content_info_bytes, +}; +pub mod projection; diff --git a/src/ccr/model.rs b/src/ccr/model.rs new file mode 100644 index 0000000..302793a --- /dev/null +++ b/src/ccr/model.rs @@ -0,0 +1,398 @@ +use crate::model::common::{BigUnsigned, der_take_tlv}; +use crate::model::oid::{OID_CT_RPKI_CCR, OID_SHA256}; + +pub const CCR_VERSION_V0: u32 = 0; +pub const DIGEST_LEN_SHA256: usize = 32; +pub const KEY_IDENTIFIER_LEN_SHA1: usize = 20; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum CcrDigestAlgorithm { + Sha256, +} + +impl CcrDigestAlgorithm { + pub fn oid(&self) -> &'static str { + match self { + Self::Sha256 => OID_SHA256, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CcrContentInfo { + pub content_type_oid: String, + pub content: RpkiCanonicalCacheRepresentation, +} + +impl CcrContentInfo { + pub fn new(content: RpkiCanonicalCacheRepresentation) -> Self { + Self { + content_type_oid: OID_CT_RPKI_CCR.to_string(), + content, + } + } + + pub fn validate(&self) -> Result<(), String> { + if self.content_type_oid != OID_CT_RPKI_CCR { + return Err(format!( + "contentType must be {OID_CT_RPKI_CCR}, got {}", + self.content_type_oid + )); + } + self.content.validate() + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RpkiCanonicalCacheRepresentation { + pub version: u32, + pub hash_alg: CcrDigestAlgorithm, + pub produced_at: time::OffsetDateTime, + pub mfts: Option, + pub vrps: Option, + pub vaps: Option, + pub tas: Option, + pub rks: Option, +} + +impl RpkiCanonicalCacheRepresentation { + pub fn validate(&self) -> Result<(), String> { + if self.version != CCR_VERSION_V0 { + return Err(format!("CCR version must be 0, got {}", self.version)); + } + if !matches!(self.hash_alg, CcrDigestAlgorithm::Sha256) { + return Err("CCR hashAlg must be SHA-256".into()); + } + if self.mfts.is_none() + && self.vrps.is_none() + && self.vaps.is_none() + && self.tas.is_none() + && self.rks.is_none() + { + return Err("at least one of mfts/vrps/vaps/tas/rks must be present".into()); + } + if let Some(mfts) = &self.mfts { + mfts.validate()?; + } + if let Some(vrps) = &self.vrps { + vrps.validate()?; + } + if let Some(vaps) = &self.vaps { + vaps.validate()?; + } + if let Some(tas) = &self.tas { + tas.validate()?; + } + if let Some(rks) = &self.rks { + rks.validate()?; + } + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ManifestState { + pub mis: Vec, + pub most_recent_update: time::OffsetDateTime, + pub hash: Vec, +} + +impl ManifestState { + pub fn validate(&self) -> Result<(), String> { + validate_sha256_digest("ManifestState.hash", &self.hash)?; + validate_sorted_unique_by( + &self.mis, + |item| item.hash.as_slice(), + "ManifestState.mis must be sorted by hash and unique", + )?; + for instance in &self.mis { + instance.validate()?; + } + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ManifestInstance { + pub hash: Vec, + pub size: u64, + pub aki: Vec, + pub manifest_number: BigUnsigned, + pub this_update: time::OffsetDateTime, + pub locations: Vec>, + pub subordinates: Vec>, +} + +impl ManifestInstance { + pub fn validate(&self) -> Result<(), String> { + validate_sha256_digest("ManifestInstance.hash", &self.hash)?; + if self.size < 1000 { + return Err(format!( + "ManifestInstance.size must be >= 1000, got {}", + self.size + )); + } + validate_key_identifier("ManifestInstance.aki", &self.aki)?; + validate_big_unsigned_bytes( + "ManifestInstance.manifest_number", + &self.manifest_number.bytes_be, + )?; + if self.locations.is_empty() { + return Err( + "ManifestInstance.locations must contain at least one AccessDescription".into(), + ); + } + for location in &self.locations { + validate_full_der_with_tag("ManifestInstance.locations[]", location, Some(0x30))?; + } + if !self.subordinates.is_empty() { + validate_sorted_unique_bytes( + &self.subordinates, + KEY_IDENTIFIER_LEN_SHA1, + "ManifestInstance.subordinates must be sorted/unique 20-byte SKIs", + )?; + } + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RoaPayloadState { + pub rps: Vec, + pub hash: Vec, +} + +impl RoaPayloadState { + pub fn validate(&self) -> Result<(), String> { + validate_sha256_digest("ROAPayloadState.hash", &self.hash)?; + validate_sorted_unique_by( + &self.rps, + |item| &item.as_id, + "ROAPayloadState.rps must be sorted by asID and unique", + )?; + for set in &self.rps { + set.validate()?; + } + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RoaPayloadSet { + pub as_id: u32, + pub ip_addr_blocks: Vec>, +} + +impl RoaPayloadSet { + pub fn validate(&self) -> Result<(), String> { + if self.ip_addr_blocks.is_empty() || self.ip_addr_blocks.len() > 2 { + return Err(format!( + "ROAPayloadSet.ip_addr_blocks must contain 1..=2 entries, got {}", + self.ip_addr_blocks.len() + )); + } + for block in &self.ip_addr_blocks { + validate_full_der_with_tag("ROAPayloadSet.ip_addr_blocks[]", block, Some(0x30))?; + } + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AspaPayloadState { + pub aps: Vec, + pub hash: Vec, +} + +impl AspaPayloadState { + pub fn validate(&self) -> Result<(), String> { + validate_sha256_digest("ASPAPayloadState.hash", &self.hash)?; + validate_sorted_unique_by( + &self.aps, + |item| &item.customer_as_id, + "ASPAPayloadState.aps must be sorted by customerASID and unique", + )?; + for set in &self.aps { + set.validate()?; + } + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AspaPayloadSet { + pub customer_as_id: u32, + pub providers: Vec, +} + +impl AspaPayloadSet { + pub fn validate(&self) -> Result<(), String> { + if self.providers.is_empty() { + return Err("ASPAPayloadSet.providers must be non-empty".into()); + } + validate_sorted_unique_by( + &self.providers, + |provider| provider, + "ASPAPayloadSet.providers must be sorted ascending and unique", + ) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TrustAnchorState { + pub skis: Vec>, + pub hash: Vec, +} + +impl TrustAnchorState { + pub fn validate(&self) -> Result<(), String> { + if self.skis.is_empty() { + return Err("TrustAnchorState.skis must be non-empty".into()); + } + validate_sha256_digest("TrustAnchorState.hash", &self.hash)?; + validate_sorted_unique_bytes( + &self.skis, + KEY_IDENTIFIER_LEN_SHA1, + "TrustAnchorState.skis must be sorted/unique 20-byte SKIs", + ) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RouterKeyState { + pub rksets: Vec, + pub hash: Vec, +} + +impl RouterKeyState { + pub fn validate(&self) -> Result<(), String> { + validate_sha256_digest("RouterKeyState.hash", &self.hash)?; + validate_sorted_unique_by( + &self.rksets, + |item| &item.as_id, + "RouterKeyState.rksets must be sorted by asID and unique", + )?; + for rkset in &self.rksets { + rkset.validate()?; + } + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RouterKeySet { + pub as_id: u32, + pub router_keys: Vec, +} + +impl RouterKeySet { + pub fn validate(&self) -> Result<(), String> { + if self.router_keys.is_empty() { + return Err("RouterKeySet.router_keys must be non-empty".into()); + } + validate_sorted_unique_by( + &self.router_keys, + |key| key, + "RouterKeySet.router_keys must be sorted by SKI and unique by (SKI, SPKI DER)", + )?; + for key in &self.router_keys { + key.validate()?; + } + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct RouterKey { + pub ski: Vec, + pub spki_der: Vec, +} + +impl RouterKey { + pub fn validate(&self) -> Result<(), String> { + validate_key_identifier("RouterKey.ski", &self.ski)?; + validate_full_der_with_tag("RouterKey.spki_der", &self.spki_der, Some(0x30)) + } +} + +fn validate_sha256_digest(field: &str, bytes: &[u8]) -> Result<(), String> { + if bytes.len() != DIGEST_LEN_SHA256 { + return Err(format!( + "{field} must be {DIGEST_LEN_SHA256} bytes, got {}", + bytes.len() + )); + } + Ok(()) +} + +fn validate_key_identifier(field: &str, bytes: &[u8]) -> Result<(), String> { + if bytes.len() != KEY_IDENTIFIER_LEN_SHA1 { + return Err(format!( + "{field} must be {KEY_IDENTIFIER_LEN_SHA1} bytes, got {}", + bytes.len() + )); + } + Ok(()) +} + +fn validate_big_unsigned_bytes(field: &str, bytes: &[u8]) -> Result<(), String> { + if bytes.is_empty() { + return Err(format!("{field} must not be empty")); + } + if bytes.len() > 1 && bytes[0] == 0x00 { + return Err(format!( + "{field} must be minimally encoded as an unsigned integer" + )); + } + Ok(()) +} + +fn validate_sorted_unique_by( + values: &[T], + key_fn: impl Fn(&T) -> &K, + message: &str, +) -> Result<(), String> { + for window in values.windows(2) { + if key_fn(&window[0]) >= key_fn(&window[1]) { + return Err(message.to_string()); + } + } + Ok(()) +} + +fn validate_sorted_unique_bytes( + values: &[Vec], + expected_len: usize, + message: &str, +) -> Result<(), String> { + for value in values { + if value.len() != expected_len { + return Err(message.to_string()); + } + } + for window in values.windows(2) { + if window[0] >= window[1] { + return Err(message.to_string()); + } + } + Ok(()) +} + +fn validate_full_der_with_tag( + field: &str, + der: &[u8], + expected_tag: Option, +) -> Result<(), String> { + let (tag, _value, rem) = der_take_tlv(der).map_err(|e| format!("{field}: {e}"))?; + if !rem.is_empty() { + return Err(format!("{field}: trailing bytes after DER object")); + } + if let Some(expected_tag) = expected_tag + && tag != expected_tag + { + return Err(format!( + "{field}: unexpected tag 0x{tag:02X}, expected 0x{expected_tag:02X}" + )); + } + Ok(()) +} diff --git a/src/ccr/projection.rs b/src/ccr/projection.rs new file mode 100644 index 0000000..4df1b02 --- /dev/null +++ b/src/ccr/projection.rs @@ -0,0 +1,69 @@ +//! Manifest contribution to CCR, independent of validation-result persistence. +use crate::repository::storage::PackTime; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct CcrManifestProjection { + pub manifest_rsync_uri: String, + pub manifest_sha256: Vec, + pub manifest_size: u64, + pub manifest_ee_aki: Vec, + pub manifest_number_be: Vec, + pub manifest_this_update: PackTime, + pub manifest_sia_locations_der: Vec>, + pub subordinate_skis: Vec>, +} + +use crate::ccr::manifest_location::select_manifest_signed_object_location; +use crate::model::manifest::ManifestObject; +use crate::model::rc::SubjectInfoAccess; +use crate::validation::publication_point::PublicationPointSnapshot; +use crate::validation::tree::CaInstanceHandle; +use sha2::Digest; + +pub(crate) fn from_snapshot( + ca: &CaInstanceHandle, + pack: &PublicationPointSnapshot, + mut subordinate_skis: Vec>, +) -> Result { + let manifest = ManifestObject::decode_der(&pack.manifest_bytes) + .map_err(|e| format!("decode manifest for CCR projection failed: {e}"))?; + let ee = &manifest.signed_object.signed_data.certificates[0].resource_cert; + let manifest_ee_aki = ee + .tbs + .extensions + .authority_key_identifier + .clone() + .ok_or_else(|| "manifest EE certificate missing AuthorityKeyIdentifier".to_string())?; + let manifest_sia_locations_der = match ee + .tbs + .extensions + .subject_info_access + .as_ref() + .ok_or_else(|| "manifest EE certificate missing Subject Information Access".to_string())? + { + SubjectInfoAccess::Ee(ee_sia) => vec![select_manifest_signed_object_location( + &ca.manifest_rsync_uri, + &ee_sia.access_descriptions, + )?], + SubjectInfoAccess::Ca(_) => { + return Err( + "manifest EE certificate Subject Information Access has CA variant".to_string(), + ); + } + }; + + subordinate_skis.sort(); + subordinate_skis.dedup(); + + Ok(CcrManifestProjection { + manifest_rsync_uri: ca.manifest_rsync_uri.clone(), + manifest_sha256: sha2::Sha256::digest(&pack.manifest_bytes).to_vec(), + manifest_size: pack.manifest_bytes.len() as u64, + manifest_ee_aki, + manifest_number_be: pack.manifest_number_be.clone(), + manifest_this_update: pack.this_update.clone(), + manifest_sia_locations_der, + subordinate_skis, + }) +} diff --git a/src/ccr/verify.rs b/src/ccr/verify.rs new file mode 100644 index 0000000..65594a7 --- /dev/null +++ b/src/ccr/verify.rs @@ -0,0 +1,359 @@ +#![allow(clippy::type_complexity)] + +use crate::ccr::decode::{CcrDecodeError, decode_content_info}; +use crate::ccr::encode::{ + encode_aspa_payload_state_payload_der, encode_manifest_state_payload_der, + encode_roa_payload_state_payload_der, encode_router_key_state_payload_der, + encode_trust_anchor_state_payload_der, +}; +use crate::ccr::hash::verify_state_hash; +use crate::ccr::model::{CcrContentInfo, RouterKeyState, TrustAnchorState}; +use serde::Serialize; +use std::collections::BTreeSet; +use std::path::Path; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct CcrVerifySummary { + pub content_type_oid: String, + pub version: u32, + pub produced_at_rfc3339_utc: String, + pub state_hashes_ok: bool, + pub manifest_instances: usize, + pub roa_payload_sets: usize, + pub roa_vrp_count: usize, + pub aspa_payload_sets: usize, + pub trust_anchor_ski_count: usize, + pub router_key_sets: usize, + pub router_key_count: usize, +} + +#[derive(Debug, thiserror::Error)] +pub enum CcrVerifyError { + #[error("CCR decode failed: {0}")] + Decode(#[from] CcrDecodeError), + + #[error("ManifestState hash mismatch")] + ManifestHashMismatch, + + #[error("ROAPayloadState hash mismatch")] + RoaHashMismatch, + + #[error("ASPAPayloadState hash mismatch")] + AspaHashMismatch, + + #[error("TrustAnchorState hash mismatch")] + TrustAnchorHashMismatch, + + #[error("RouterKeyState hash mismatch")] + RouterKeyHashMismatch, + + #[error("read report json failed: {0}: {1}")] + ReportRead(String, String), + + #[error("parse report json failed: {0}")] + ReportParse(String), + + #[error("VRP set mismatch: only_in_ccr={only_in_ccr} only_in_report={only_in_report}")] + ReportVrpMismatch { + only_in_ccr: usize, + only_in_report: usize, + }, + + #[error("ASPA set mismatch: only_in_ccr={only_in_ccr} only_in_report={only_in_report}")] + ReportAspaMismatch { + only_in_ccr: usize, + only_in_report: usize, + }, +} + +pub fn verify_content_info_bytes(der: &[u8]) -> Result { + let content_info = decode_content_info(der)?; + verify_content_info(&content_info) +} + +pub fn verify_content_info( + content_info: &CcrContentInfo, +) -> Result { + content_info.validate().map_err(CcrDecodeError::Validate)?; + let state_hashes_ok = true; + let mut manifest_instances = 0usize; + let mut roa_payload_sets = 0usize; + let mut roa_vrp_count = 0usize; + let mut aspa_payload_sets = 0usize; + let mut trust_anchor_ski_count = 0usize; + let mut router_key_sets = 0usize; + let mut router_key_count = 0usize; + + if let Some(mfts) = &content_info.content.mfts { + let payload_der = encode_manifest_state_payload_der(&mfts.mis) + .map_err(|e| CcrVerifyError::Decode(CcrDecodeError::Validate(e.to_string())))?; + if !verify_state_hash(&mfts.hash, &payload_der) { + return Err(CcrVerifyError::ManifestHashMismatch); + } + manifest_instances = mfts.mis.len(); + } + if let Some(vrps) = &content_info.content.vrps { + let payload_der = encode_roa_payload_state_payload_der(&vrps.rps) + .map_err(|e| CcrVerifyError::Decode(CcrDecodeError::Validate(e.to_string())))?; + if !verify_state_hash(&vrps.hash, &payload_der) { + return Err(CcrVerifyError::RoaHashMismatch); + } + roa_payload_sets = vrps.rps.len(); + roa_vrp_count = vrps + .rps + .iter() + .map(|set| count_roa_block_entries(&set.ip_addr_blocks)) + .sum(); + } + if let Some(vaps) = &content_info.content.vaps { + let payload_der = encode_aspa_payload_state_payload_der(&vaps.aps) + .map_err(|e| CcrVerifyError::Decode(CcrDecodeError::Validate(e.to_string())))?; + if !verify_state_hash(&vaps.hash, &payload_der) { + return Err(CcrVerifyError::AspaHashMismatch); + } + aspa_payload_sets = vaps.aps.len(); + } + if let Some(tas) = &content_info.content.tas { + verify_trust_anchor_state_hash(tas)?; + trust_anchor_ski_count = tas.skis.len(); + } + if let Some(rks) = &content_info.content.rks { + verify_router_key_state_hash(rks)?; + router_key_sets = rks.rksets.len(); + router_key_count = rks.rksets.iter().map(|set| set.router_keys.len()).sum(); + } + + let produced_at_rfc3339_utc = content_info + .content + .produced_at + .to_offset(time::UtcOffset::UTC) + .format(&time::format_description::well_known::Rfc3339) + .map_err(|e| CcrVerifyError::Decode(CcrDecodeError::Validate(e.to_string())))?; + + Ok(CcrVerifySummary { + content_type_oid: content_info.content_type_oid.clone(), + version: content_info.content.version, + produced_at_rfc3339_utc, + state_hashes_ok, + manifest_instances, + roa_payload_sets, + roa_vrp_count, + aspa_payload_sets, + trust_anchor_ski_count, + router_key_sets, + router_key_count, + }) +} + +pub fn verify_against_report_json_path( + content_info: &CcrContentInfo, + report_json_path: &Path, +) -> Result<(), CcrVerifyError> { + let bytes = std::fs::read(report_json_path).map_err(|e| { + CcrVerifyError::ReportRead(report_json_path.display().to_string(), e.to_string()) + })?; + let json: serde_json::Value = + serde_json::from_slice(&bytes).map_err(|e| CcrVerifyError::ReportParse(e.to_string()))?; + + let report_vrps = report_vrp_keys(&json)?; + let ccr_vrps = extract_vrp_rows(content_info)?; + let only_in_ccr = ccr_vrps.difference(&report_vrps).count(); + let only_in_report = report_vrps.difference(&ccr_vrps).count(); + if only_in_ccr != 0 || only_in_report != 0 { + return Err(CcrVerifyError::ReportVrpMismatch { + only_in_ccr, + only_in_report, + }); + } + + let report_aspas = report_aspa_keys(&json)?; + let ccr_aspas = ccr_aspa_keys(content_info)?; + let only_in_ccr = ccr_aspas.difference(&report_aspas).count(); + let only_in_report = report_aspas.difference(&ccr_aspas).count(); + if only_in_ccr != 0 || only_in_report != 0 { + return Err(CcrVerifyError::ReportAspaMismatch { + only_in_ccr, + only_in_report, + }); + } + Ok(()) +} + +fn verify_trust_anchor_state_hash(state: &TrustAnchorState) -> Result<(), CcrVerifyError> { + let payload_der = encode_trust_anchor_state_payload_der(&state.skis) + .map_err(|e| CcrVerifyError::Decode(CcrDecodeError::Validate(e.to_string())))?; + if !verify_state_hash(&state.hash, &payload_der) { + return Err(CcrVerifyError::TrustAnchorHashMismatch); + } + Ok(()) +} + +fn verify_router_key_state_hash(state: &RouterKeyState) -> Result<(), CcrVerifyError> { + let payload_der = encode_router_key_state_payload_der(&state.rksets) + .map_err(|e| CcrVerifyError::Decode(CcrDecodeError::Validate(e.to_string())))?; + if !verify_state_hash(&state.hash, &payload_der) { + return Err(CcrVerifyError::RouterKeyHashMismatch); + } + Ok(()) +} + +fn report_vrp_keys( + json: &serde_json::Value, +) -> Result, CcrVerifyError> { + let mut out = BTreeSet::new(); + let Some(items) = json.get("vrps").and_then(|v| v.as_array()) else { + return Ok(out); + }; + for item in items { + let asn = item + .get("asn") + .and_then(|v| v.as_u64()) + .ok_or_else(|| CcrVerifyError::ReportParse("vrps[].asn missing".into()))? + as u32; + let prefix = item + .get("prefix") + .and_then(|v| v.as_str()) + .ok_or_else(|| CcrVerifyError::ReportParse("vrps[].prefix missing".into()))? + .to_string(); + let max_length = item + .get("max_length") + .and_then(|v| v.as_u64()) + .ok_or_else(|| CcrVerifyError::ReportParse("vrps[].max_length missing".into()))? + as u16; + out.insert((asn, prefix, max_length)); + } + Ok(out) +} + +fn report_aspa_keys(json: &serde_json::Value) -> Result)>, CcrVerifyError> { + let mut out = BTreeSet::new(); + let Some(items) = json.get("aspas").and_then(|v| v.as_array()) else { + return Ok(out); + }; + for item in items { + let customer = item + .get("customer_as_id") + .and_then(|v| v.as_u64()) + .ok_or_else(|| CcrVerifyError::ReportParse("aspas[].customer_as_id missing".into()))? + as u32; + let mut providers = item + .get("provider_as_ids") + .and_then(|v| v.as_array()) + .ok_or_else(|| CcrVerifyError::ReportParse("aspas[].provider_as_ids missing".into()))? + .iter() + .map(|v| { + v.as_u64() + .ok_or_else(|| CcrVerifyError::ReportParse("provider_as_ids[] invalid".into())) + .map(|v| v as u32) + }) + .collect::, _>>()?; + providers.sort_unstable(); + providers.dedup(); + out.insert((customer, providers)); + } + Ok(out) +} + +pub fn extract_vrp_rows( + content_info: &CcrContentInfo, +) -> Result, CcrVerifyError> { + let mut out = BTreeSet::new(); + let Some(vrps) = &content_info.content.vrps else { + return Ok(out); + }; + for set in &vrps.rps { + for block in &set.ip_addr_blocks { + let (afi, entries) = decode_roa_family_block(block)?; + for (prefix_len, addr_bytes, max_len) in entries { + let prefix = format_prefix(afi, &addr_bytes, prefix_len)?; + out.insert((set.as_id, prefix, max_len.unwrap_or(prefix_len as u16))); + } + } + } + Ok(out) +} + +fn ccr_aspa_keys( + content_info: &CcrContentInfo, +) -> Result)>, CcrVerifyError> { + let mut out = BTreeSet::new(); + let Some(vaps) = &content_info.content.vaps else { + return Ok(out); + }; + for set in &vaps.aps { + out.insert((set.customer_as_id, set.providers.clone())); + } + Ok(out) +} + +fn decode_roa_family_block( + block: &[u8], +) -> Result<(u16, Vec<(u8, Vec, Option)>), CcrVerifyError> { + let mut top = crate::model::common::DerReader::new(block); + let mut seq = top + .take_sequence() + .map_err(|e| CcrVerifyError::Decode(CcrDecodeError::Parse(e)))?; + if !top.is_empty() { + return Err(CcrVerifyError::Decode(CcrDecodeError::Parse( + "trailing bytes after ROAIPAddressFamily".into(), + ))); + } + let afi_bytes = seq + .take_octet_string() + .map_err(|e| CcrVerifyError::Decode(CcrDecodeError::Parse(e)))?; + let afi = u16::from_be_bytes([afi_bytes[0], afi_bytes[1]]); + let mut addrs = seq + .take_sequence() + .map_err(|e| CcrVerifyError::Decode(CcrDecodeError::Parse(e)))?; + let mut entries = Vec::new(); + while !addrs.is_empty() { + let mut addr_seq = addrs + .take_sequence() + .map_err(|e| CcrVerifyError::Decode(CcrDecodeError::Parse(e)))?; + let (unused_bits, content) = addr_seq + .take_bit_string() + .map_err(|e| CcrVerifyError::Decode(CcrDecodeError::Parse(e)))?; + let prefix_len = (content.len() * 8) as u8 - unused_bits; + let max_len = if addr_seq.is_empty() { + None + } else { + Some( + addr_seq + .take_uint_u64() + .map_err(|e| CcrVerifyError::Decode(CcrDecodeError::Parse(e)))? + as u16, + ) + }; + entries.push((prefix_len, content.to_vec(), max_len)); + } + Ok((afi, entries)) +} + +fn format_prefix(afi: u16, addr_bytes: &[u8], prefix_len: u8) -> Result { + match afi { + 1 => { + let mut full = [0u8; 4]; + full[..addr_bytes.len()].copy_from_slice(addr_bytes); + Ok(format!("{}/{prefix_len}", std::net::Ipv4Addr::from(full))) + } + 2 => { + let mut full = [0u8; 16]; + full[..addr_bytes.len()].copy_from_slice(addr_bytes); + Ok(format!("{}/{prefix_len}", std::net::Ipv6Addr::from(full))) + } + other => Err(CcrVerifyError::Decode(CcrDecodeError::Parse(format!( + "unsupported AFI {other}" + )))), + } +} + +fn count_roa_block_entries(blocks: &[Vec]) -> usize { + blocks + .iter() + .map(|block| { + decode_roa_family_block(block) + .map(|(_, entries)| entries.len()) + .unwrap_or(0) + }) + .sum() +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs new file mode 100644 index 0000000..7d0dd4c --- /dev/null +++ b/src/cli/mod.rs @@ -0,0 +1,226 @@ +//! Narrow command-line surface for the v0.1.0 RFC core. + +mod validate; + +/// Reject unrelated state layouts before selecting a synchronization mode. +pub(crate) fn check_state_root(root: &std::path::Path) -> Result<(), String> { + if !root.exists() { + return Ok(()); + } + for entry in std::fs::read_dir(root).map_err(|e| e.to_string())? { + let entry = entry.map_err(|e| e.to_string())?; + if entry.file_name() != "repository-db" + || !entry.file_type().map_err(|e| e.to_string())?.is_dir() + { + return Err(format!( + "unrecognized state layout at {}; use a fresh state directory", + root.display() + )); + } + } + Ok(()) +} + +pub(crate) use validate::{VALIDATE_OPTIONS, VALIDATE_REPEATABLE}; + +/// Bounded default long enough for large public RIR snapshots. +const DEFAULT_HTTP_TIMEOUT_SECS: u64 = 300; +const DEFAULT_WORKER_COUNT: usize = 8; +const DEFAULT_REPO_SYNC_WORKER_COUNT: usize = 8; +const DEFAULT_WORKER_QUEUE_CAPACITY: usize = 256; + +pub fn usage() -> String { + let validation = format!( + "panda-rpki {}\n\nUSAGE:\n panda-rpki validate --tal --ta [--tal --ta ...] --out [--rrdp-state-dir ] [--rrdp-sync-mode auto|snapshot|delta] [--ccr-out ] [--parallel-phase2-object-workers ] [--parallel-max-repo-sync-workers-global ] [--parallel-phase2-worker-queue-capacity ] [--parallel-repo-worker-queue-capacity ] [--max-ca-depth ] [--http-timeout-secs ] [--http-root-cert ]... [--log-level off|error|warn|info|debug|trace] [--log-format text|json]\n\nTAL/TA pairs are positional and may be repeated. RRDP state is an explicit protocol-resume directory, not a validation cache. The public v0.1.0 fast path accepts HTTPS RRDP notification/snapshot/delta URLs only.", + crate::VERSION + ); + format!("{validation}\n\n{}", crate::daemon::usage()) +} + +pub fn run(args: I) -> Result<(), String> +where + I: IntoIterator, + S: Into, +{ + let args: Vec = args.into_iter().map(Into::into).collect(); + if args.is_empty() || matches!(args.first().map(String::as_str), Some("--help" | "-h")) { + return Err(usage()); + } + match args[0].as_str() { + "validate" => validate::run(&args[1..]), + "daemon" => crate::daemon::run(&args[1..]), + _ => Err(format!("unknown command: {}", args[0])), + } +} + +#[derive(Debug)] +pub(crate) struct Options { + values: std::collections::BTreeMap>, +} + +impl Options { + pub(crate) fn parse( + args: &[String], + allowed: &[&str], + repeatable: &[&str], + ) -> Result { + let mut values = std::collections::BTreeMap::>::new(); + let mut index = 0; + while index < args.len() { + let flag = &args[index]; + if matches!(flag.as_str(), "--help" | "-h") { + return Err(usage()); + } + let name = flag + .strip_prefix("--") + .ok_or_else(|| format!("expected an option, got {flag}"))?; + if !allowed.contains(&name) { + return Err(format!("unsupported option: {flag}")); + } + index += 1; + let value = args + .get(index) + .ok_or_else(|| format!("{flag} requires a value"))?; + if value.starts_with("--") { + return Err(format!("{flag} requires a value")); + } + let entry = values.entry(name.to_string()).or_default(); + if !repeatable.contains(&name) && !entry.is_empty() { + return Err(format!("option repeated: {flag}")); + } + entry.push(value.clone()); + index += 1; + } + Ok(Self { values }) + } + + pub(crate) fn required(&self, name: &str) -> Result<&str, String> { + self.values + .get(name) + .and_then(|values| values.first()) + .map(String::as_str) + .ok_or_else(|| format!("--{name} is required")) + } + + pub(crate) fn values(&self, name: &str) -> &[String] { + self.values.get(name).map(Vec::as_slice).unwrap_or(&[]) + } + + pub(crate) fn parse_or(&self, name: &str, default: T) -> Result + where + T: std::str::FromStr, + T::Err: std::fmt::Display, + { + self.values + .get(name) + .and_then(|values| values.first()) + .map(|value| { + value + .parse() + .map_err(|error| format!("invalid --{name}: {error}")) + }) + .unwrap_or(Ok(default)) + } + + pub(crate) fn optional_parse(&self, name: &str) -> Result, String> + where + T: std::str::FromStr, + T::Err: std::fmt::Display, + { + self.values + .get(name) + .and_then(|values| values.first()) + .map(|value| { + value + .parse() + .map(Some) + .map_err(|error| format!("invalid --{name}: {error}")) + }) + .unwrap_or(Ok(None)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn state_layout_rejects_unrecognized_entries_without_modifying_them() { + let root = tempfile::tempdir().unwrap(); + check_state_root(root.path()).unwrap(); + std::fs::create_dir(root.path().join("repository-db")).unwrap(); + check_state_root(root.path()).unwrap(); + let unknown = root.path().join("previous-state"); + std::fs::create_dir(&unknown).unwrap(); + std::fs::write(unknown.join("checkpoint"), b"retain").unwrap(); + assert!( + check_state_root(root.path()) + .unwrap_err() + .contains("unrecognized state layout") + ); + assert_eq!( + std::fs::read(unknown.join("checkpoint")).unwrap(), + b"retain" + ); + } + + #[test] + fn parser_rejects_private_or_unknown_options() { + let error = Options::parse(&["--extra".to_string(), "1".to_string()], &["out"], &[]) + .expect_err("unknown option"); + assert!(error.contains("unsupported option")); + } + + #[test] + fn parser_preserves_repeatable_tal_pairs() { + let options = Options::parse( + &[ + "--tal".to_string(), + "one.tal".to_string(), + "--ta".to_string(), + "one.cer".to_string(), + "--tal".to_string(), + "two.tal".to_string(), + "--ta".to_string(), + "two.cer".to_string(), + ], + &["tal", "ta"], + &["tal", "ta"], + ) + .expect("repeatable pairs"); + assert_eq!(options.values("tal"), ["one.tal", "two.tal"]); + assert_eq!(options.values("ta"), ["one.cer", "two.cer"]); + } + + #[test] + fn parser_rejects_non_repeatable_worker_option() { + let error = Options::parse( + &[ + "--workers".to_string(), + "2".to_string(), + "--workers".to_string(), + "3".to_string(), + ], + &["workers"], + &[], + ) + .expect_err("worker option should not repeat"); + assert!(error.contains("option repeated")); + } + + #[test] + fn parser_preserves_repeatable_http_root_certificates() { + let options = Options::parse( + &[ + "--http-root-cert".to_string(), + "one.pem".to_string(), + "--http-root-cert".to_string(), + "two.pem".to_string(), + ], + &["http-root-cert"], + &["http-root-cert"], + ) + .expect("repeatable HTTP roots"); + assert_eq!(options.values("http-root-cert"), ["one.pem", "two.pem"]); + } +} diff --git a/src/cli/validate.rs b/src/cli/validate.rs new file mode 100644 index 0000000..941e51d --- /dev/null +++ b/src/cli/validate.rs @@ -0,0 +1,276 @@ +//! Parse validation options into typed runtime configuration. + +use std::fs::{self, File, OpenOptions}; +use std::io::{BufRead, BufReader, BufWriter, Write}; +use std::path::{Path, PathBuf}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum SyncMode { + Auto, + Snapshot, + Delta, +} + +pub(crate) const VALIDATE_OPTIONS: &[&str] = &[ + "tal", + "log-level", + "log-format", + "ta", + "tal-id", + "ta-constraints", + "out", + "ccr-out", + "rrdp-state-dir", + "rrdp-sync-mode", + "max-ca-depth", + "http-timeout-secs", + "http-root-cert", + "parallel-max-repo-sync-workers-global", + "parallel-phase2-object-workers", + "parallel-phase2-worker-queue-capacity", + "parallel-repo-worker-queue-capacity", +]; +pub(crate) const VALIDATE_REPEATABLE: &[&str] = + &["tal", "ta", "tal-id", "ta-constraints", "http-root-cert"]; + +pub(super) fn run(args: &[String]) -> Result<(), String> { + let options = super::Options::parse(args, VALIDATE_OPTIONS, VALIDATE_REPEATABLE)?; + let level = resolve_setting( + options.values("log-level"), + std::env::var("PANDA_RPKI_LOG_LEVEL").ok(), + "info", + ); + let format = resolve_setting( + options.values("log-format"), + std::env::var("PANDA_RPKI_LOG_FORMAT").ok(), + "text", + ); + crate::logging::configure(&level, &format)?; + let tal_paths = options.values("tal"); + let ta_paths = options.values("ta"); + if tal_paths.is_empty() || ta_paths.is_empty() || tal_paths.len() != ta_paths.len() { + return Err("--tal and --ta must be repeated as equal-length positional pairs".to_string()); + } + let requested_ids = options.values("tal-id"); + if !requested_ids.is_empty() && requested_ids.len() != tal_paths.len() { + return Err("--tal-id must be supplied once for each --tal when used".to_string()); + } + let max_ca_depth = options.parse_or("max-ca-depth", 64_usize)?; + let timeout = options.parse_or("http-timeout-secs", super::DEFAULT_HTTP_TIMEOUT_SECS)?; + let workers = options + .optional_parse("parallel-phase2-object-workers")? + .unwrap_or(super::DEFAULT_WORKER_COUNT); + let repo_workers = options + .optional_parse("parallel-max-repo-sync-workers-global")? + .unwrap_or(super::DEFAULT_REPO_SYNC_WORKER_COUNT); + let phase2_queue = options.parse_or( + "parallel-phase2-worker-queue-capacity", + super::DEFAULT_WORKER_QUEUE_CAPACITY, + )?; + let repo_queue = options.parse_or( + "parallel-repo-worker-queue-capacity", + super::DEFAULT_WORKER_QUEUE_CAPACITY, + )?; + if workers == 0 || repo_workers == 0 || phase2_queue == 0 || repo_queue == 0 { + return Err("worker counts and queue capacities must be greater than zero".to_string()); + } + + let out = PathBuf::from(options.required("out")?); + fs::create_dir_all(&out) + .map_err(|error| format!("create output directory {}: {error}", out.display()))?; + let state_root = options + .values("rrdp-state-dir") + .first() + .map(PathBuf::from) + .unwrap_or_else(|| out.join(".state")); + fs::create_dir_all(&state_root) + .map_err(|error| format!("create state directory {}: {error}", state_root.display()))?; + let db_path = state_root.join("repository-db"); + super::check_state_root(&state_root)?; + let sync_mode = parse_sync_mode(options.values("rrdp-sync-mode"))?; + if sync_mode == SyncMode::Snapshot && directory_has_entries(&db_path)? { + return Err(format!( + "snapshot mode requires a fresh fast-path state directory; existing database: {}", + db_path.display() + )); + } + if sync_mode == SyncMode::Delta && !directory_has_entries(&db_path)? { + return Err(format!( + "delta mode requires an existing fast-path state database: {}", + db_path.display() + )); + } + + use crate::runtime::RunConfig; + use crate::scheduler::config::{ParallelPhase1Config, ParallelPhase2Config}; + use crate::scheduler::types::TalInputSpec; + let mut tal_inputs: Vec<_> = tal_paths + .iter() + .zip(ta_paths) + .map(|(tal, ta)| TalInputSpec::from_file_path_with_ta(tal, ta)) + .collect(); + for (input, id) in tal_inputs.iter_mut().zip(requested_ids) { + input.tal_id = id.clone(); + } + let tal_ids: Vec = tal_inputs + .iter() + .map(|input| input.tal_id.clone()) + .collect(); + let ta_constraints = crate::ta_constraints::TaConstraintsByTal::load_for_tals( + &tal_inputs, + options.values("ta-constraints"), + )?; + let config = RunConfig { + db_path, + tal_paths: tal_paths.iter().map(PathBuf::from).collect(), + ta_paths: ta_paths.iter().map(PathBuf::from).collect(), + tal_path: tal_paths.first().map(PathBuf::from), + ta_path: ta_paths.first().map(PathBuf::from), + tal_inputs, + ta_constraints, + parallel_phase1_config: ParallelPhase1Config { + max_repo_sync_workers_global: repo_workers, + max_pending_repo_results: repo_queue.max(1024), + ..Default::default() + }, + parallel_phase2_config: ParallelPhase2Config { + object_workers: workers, + worker_queue_capacity: phase2_queue, + ..Default::default() + }, + max_ca_depth, + http_timeout_secs: timeout, + http_root_cert_paths: options + .values("http-root-cert") + .iter() + .map(PathBuf::from) + .collect(), + rsync_timeout_secs: 30, + summary_out_path: Some(out.join("summary.json")), + vrps_csv_out_path: Some(out.join(".vrps-source.csv")), + vaps_csv_out_path: Some(out.join("vaps.csv")), + analysis_out_path: Some(out.join("analysis")), + analyze: true, + ccr_out_path: options.values("ccr-out").first().map(PathBuf::from), + ..Default::default() + }; + + let started = std::time::Instant::now(); + crate::logging::emit(crate::logging::Level::Info, "validation_started", || { + serde_json::json!({ + "tal_count": tal_paths.len(), "tal_ids": tal_ids, "phase2_workers": workers, "repo_workers": repo_workers, + "sync_mode": format!("{sync_mode:?}").to_lowercase(), + }) + }); + crate::runtime::run_config(config).inspect_err(|_| { + crate::logging::emit(crate::logging::Level::Error, "validation_failed", || { + serde_json::json!({ + "elapsed_ms": started.elapsed().as_millis(), + }) + }); + })?; + + let fast_vrps = out.join(".vrps-source.csv"); + convert_vrp_csv(&fast_vrps, &out.join("vrps.csv"))?; + crate::logging::emit(crate::logging::Level::Info, "validation_completed", || { + serde_json::json!({ + "elapsed_ms": started.elapsed().as_millis(), "phase2_workers": workers, "repo_workers": repo_workers, + }) + }); + Ok(()) +} + +fn parse_sync_mode(values: &[String]) -> Result { + match values.first().map(String::as_str).unwrap_or("auto") { + "auto" => Ok(SyncMode::Auto), + "snapshot" => Ok(SyncMode::Snapshot), + "delta" => Ok(SyncMode::Delta), + other => Err(format!( + "--rrdp-sync-mode must be auto, snapshot, or delta; got {other}" + )), + } +} + +fn resolve_setting(cli_values: &[String], env_value: Option, default: &str) -> String { + cli_values + .first() + .cloned() + .or(env_value) + .unwrap_or_else(|| default.to_string()) +} + +fn directory_has_entries(path: &Path) -> Result { + if !path.exists() { + return Ok(false); + } + let mut entries = fs::read_dir(path) + .map_err(|error| format!("read state database {}: {error}", path.display()))?; + Ok(entries + .next() + .transpose() + .map_err(|error| format!("read state database entry {}: {error}", path.display()))? + .is_some()) +} + +fn convert_vrp_csv(input: &Path, output: &Path) -> Result<(), String> { + let input_file = File::open(input) + .map_err(|error| format!("open source VRP CSV {}: {error}", input.display()))?; + let output_file = OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(output) + .map_err(|error| format!("create Panda RPKI VRP CSV {}: {error}", output.display()))?; + let mut reader = BufReader::new(input_file); + let mut writer = BufWriter::new(output_file); + writer + .write_all(b"asn,prefix,max_length\n") + .map_err(|error| format!("write Panda RPKI VRP CSV header: {error}"))?; + let mut line = String::new(); + let mut first = true; + loop { + line.clear(); + if reader + .read_line(&mut line) + .map_err(|error| format!("read source VRP CSV: {error}"))? + == 0 + { + break; + } + if first { + first = false; + continue; + } + let mut fields = line.trim_end_matches(['\r', '\n']).splitn(4, ','); + let Some(asn) = fields.next() else { continue }; + let Some(prefix) = fields.next() else { + continue; + }; + let Some(max_length) = fields.next() else { + continue; + }; + writeln!(writer, "{asn},{prefix},{max_length}") + .map_err(|error| format!("write Panda RPKI VRP CSV: {error}"))?; + } + writer + .flush() + .map_err(|error| format!("flush Panda RPKI VRP CSV: {error}")) +} + +#[cfg(test)] +mod tests { + use super::resolve_setting; + + #[test] + fn logging_setting_precedence_is_cli_then_env_then_default() { + assert_eq!( + resolve_setting(&["debug".to_string()], Some("error".to_string()), "info"), + "debug" + ); + assert_eq!( + resolve_setting(&[], Some("json".to_string()), "text"), + "json" + ); + assert_eq!(resolve_setting(&[], None, "info"), "info"); + } +} diff --git a/src/daemon/metrics.rs b/src/daemon/metrics.rs new file mode 100644 index 0000000..e045629 --- /dev/null +++ b/src/daemon/metrics.rs @@ -0,0 +1,78 @@ +//! Process CPU time and peak memory metrics. +use serde::Serialize; +use std::fs; +use std::path::Path; +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub(super) struct ProcessMetrics { + time_wrapper_used: bool, + time_output_path: Option, + user_seconds: Option, + system_seconds: Option, + cpu_percent: Option, + elapsed_raw: Option, + max_rss_kb: Option, + exit_status_from_time: Option, + parse_error: Option, +} +pub(super) fn collect_process_metrics( + time_wrapper_used: bool, + time_output_path: &Path, +) -> ProcessMetrics { + if !time_wrapper_used { + return ProcessMetrics { + time_wrapper_used, + time_output_path: None, + user_seconds: None, + system_seconds: None, + cpu_percent: None, + elapsed_raw: None, + max_rss_kb: None, + exit_status_from_time: None, + parse_error: None, + }; + } + + let mut metrics = ProcessMetrics { + time_wrapper_used, + time_output_path: Some(time_output_path.to_string_lossy().into_owned()), + user_seconds: None, + system_seconds: None, + cpu_percent: None, + elapsed_raw: None, + max_rss_kb: None, + exit_status_from_time: None, + parse_error: None, + }; + + let text = match fs::read_to_string(time_output_path) { + Ok(text) => text, + Err(err) => { + metrics.parse_error = Some(format!( + "read process time output failed: {}: {err}", + time_output_path.display() + )); + return metrics; + } + }; + + for line in text.lines() { + let line = line.trim(); + if let Some(value) = line.strip_prefix("User time (seconds):") { + metrics.user_seconds = value.trim().parse::().ok(); + } else if let Some(value) = line.strip_prefix("System time (seconds):") { + metrics.system_seconds = value.trim().parse::().ok(); + } else if let Some(value) = line.strip_prefix("Percent of CPU this job got:") { + metrics.cpu_percent = value.trim().trim_end_matches('%').parse::().ok(); + } else if let Some(value) = + line.strip_prefix("Elapsed (wall clock) time (h:mm:ss or m:ss):") + { + metrics.elapsed_raw = Some(value.trim().to_string()); + } else if let Some(value) = line.strip_prefix("Maximum resident set size (kbytes):") { + metrics.max_rss_kb = value.trim().parse::().ok(); + } else if let Some(value) = line.strip_prefix("Exit status:") { + metrics.exit_status_from_time = value.trim().parse::().ok(); + } + } + metrics +} diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs new file mode 100644 index 0000000..fe24b8c --- /dev/null +++ b/src/daemon/mod.rs @@ -0,0 +1,374 @@ +//! Serial scheduling with an independent validator process for each run. +//! Reuses its atomic status/JSONL writers, serial loop and retention model. +//! The adapter runs the public validate command with durable protocol state. +mod metrics; +mod process; +mod storage; + +use crate::cli::{Options, VALIDATE_OPTIONS, VALIDATE_REPEATABLE}; +use crate::logging::{Level, emit}; +use serde::{Deserialize, Serialize}; +use serde_json::{Value, json}; +use std::fs::{self, File, OpenOptions}; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; +use std::time::Instant; +use storage::{append_json_line, write_json_pretty}; + +const OWNED: &[&str] = &["out", "ccr-out", "rrdp-state-dir", "rrdp-sync-mode"]; +const CONTROLLER: &[&str] = &[ + "state-root", + "interval-secs", + "max-runs", + "retain-runs", + "run-timeout-secs", + "shutdown-grace-secs", +]; + +pub(crate) fn usage() -> &'static str { + "DAEMON:\n panda-rpki daemon --state-root [--interval-secs ] [--max-runs ] [--retain-runs ] [--run-timeout-secs ] [--shutdown-grace-secs ] -- --tal --ta [validation options]\n\nDefaults: interval=60s after each run, unlimited runs, retain=10, timeout=0 (disabled), shutdown grace=30s. Reuses protocol state across runs/restarts; automatically exports CCR and CSV per run. Output/state/sync-mode options are controller-owned." +} + +struct Config { + root: PathBuf, + interval: u64, + max_runs: Option, + retain: usize, + timeout: u64, + grace: u64, + child_args: Vec, + log_level: String, + log_format: String, +} + +fn parse(args: &[String]) -> Result { + let split = args + .iter() + .position(|arg| arg == "--") + .ok_or("daemon requires -- before validation options")?; + let options = Options::parse(&args[..split], CONTROLLER, &[])?; + let child_args = args[split + 1..].to_vec(); + let child = Options::parse(&child_args, VALIDATE_OPTIONS, VALIDATE_REPEATABLE)?; + for name in OWNED { + if !child.values(name).is_empty() { + return Err(format!("daemon manages --{name}; use --state-root")); + } + } + let count = child.values("tal").len(); + if count == 0 || child.values("ta").len() != count { + return Err("--tal and --ta must be repeated as equal-length positional pairs".into()); + } + if !child.values("tal-id").is_empty() && child.values("tal-id").len() != count { + return Err("--tal-id must be supplied once for each --tal when used".into()); + } + for name in [ + "parallel-phase2-object-workers", + "parallel-max-repo-sync-workers-global", + "parallel-phase2-worker-queue-capacity", + "parallel-repo-worker-queue-capacity", + ] { + if child.parse_or(name, 1usize)? == 0 { + return Err(format!("--{name} must be greater than zero")); + } + } + child.parse_or("max-ca-depth", 64usize)?; + child.parse_or("http-timeout-secs", 300u64)?; + for (name, variable, default) in [ + ("log-level", "PANDA_RPKI_LOG_LEVEL", "info"), + ("log-format", "PANDA_RPKI_LOG_FORMAT", "text"), + ] { + let value = child + .values(name) + .first() + .cloned() + .or_else(|| std::env::var(variable).ok()) + .unwrap_or(default.into()); + if name == "log-level" { + crate::logging::parse_level(&value)?; + } else if !["text", "json"].contains(&value.as_str()) { + return Err(format!("invalid log format: {value}")); + } + } + let level = child + .values("log-level") + .first() + .cloned() + .or_else(|| std::env::var("PANDA_RPKI_LOG_LEVEL").ok()) + .unwrap_or("info".into()); + let format = child + .values("log-format") + .first() + .cloned() + .or_else(|| std::env::var("PANDA_RPKI_LOG_FORMAT").ok()) + .unwrap_or("text".into()); + let config = Config { + root: PathBuf::from(options.required("state-root")?), + interval: options.parse_or("interval-secs", 60u64)?, + max_runs: options.optional_parse("max-runs")?, + retain: options.parse_or("retain-runs", 10usize)?, + timeout: options.parse_or("run-timeout-secs", 0u64)?, + grace: options.parse_or("shutdown-grace-secs", 30u64)?, + child_args, + log_level: level, + log_format: format, + }; + if config.root.as_os_str().is_empty() || config.max_runs == Some(0) || config.retain == 0 { + return Err("state-root must be non-empty; max-runs and retain-runs must be > 0".into()); + } + Ok(config) +} + +#[derive(Default, Serialize, Deserialize)] +struct Lifecycle { + last_seq: u64, + last_success: bool, +} + +fn now() -> String { + time::OffsetDateTime::now_utc() + .format(&time::format_description::well_known::Rfc3339) + .expect("UTC timestamp formatting") +} + +fn regular_directory(path: &Path) -> Result<(), String> { + match fs::symlink_metadata(path) { + Ok(meta) if !meta.file_type().is_dir() => { + Err(format!("not a real directory: {}", path.display())) + } + Ok(_) => Ok(()), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + fs::create_dir_all(path).map_err(|e| e.to_string()) + } + Err(e) => Err(e.to_string()), + } +} + +fn read_json(path: &Path) -> Result { + let bytes = fs::read(path).map_err(|e| format!("read {}: {e}", path.display()))?; + serde_json::from_slice(&bytes).map_err(|e| format!("decode {}: {e}", path.display())) +} + +fn run_sequence(name: &str) -> Option { + let suffix = name.strip_prefix("run_")?; + if suffix.len() < 6 || !suffix.bytes().all(|b| b.is_ascii_digit()) { + return None; + } + suffix.parse().ok() +} + +fn sync_mode(state: &Path, last_success: bool) -> Result<&'static str, String> { + crate::cli::check_state_root(state)?; + let db = state.join("repository-db"); + if !db.exists() + || fs::read_dir(&db) + .map_err(|e| e.to_string())? + .next() + .transpose() + .map_err(|e| e.to_string())? + .is_none() + { + Ok("snapshot") + } else if last_success { + Ok("delta") + } else { + Ok("auto") + } +} + +// Only completed run directories owned by this controller +// are eligible. Unrelated paths, symlinks and incomplete evidence stay untouched. +fn apply_retention(runs: &Path, retain: usize) -> Result, String> { + let mut dirs = Vec::new(); + for entry in fs::read_dir(runs).map_err(|e| e.to_string())? { + let entry = entry.map_err(|e| e.to_string())?; + let name = entry.file_name().to_string_lossy().into_owned(); + if entry.file_type().map_err(|e| e.to_string())?.is_dir() + && let Some(seq) = run_sequence(&name) + && let Ok(summary) = read_json(&entry.path().join("run-summary.json")) + && summary["controller"] == "panda-rpki-daemon-v1" + && summary["run_seq"] == seq + && ["success", "failed"] + .iter() + .any(|s| summary["status"] == *s) + { + dirs.push((seq, entry.path(), name)); + } + } + dirs.sort_by_key(|x| x.0); + let remove_count = dirs.len().saturating_sub(retain); + let mut removed = Vec::new(); + for (_, path, name) in dirs.into_iter().take(remove_count) { + fs::remove_dir_all(&path).map_err(|e| format!("remove old run {}: {e}", path.display()))?; + removed.push(name); + } + Ok(removed) +} + +fn status(root: &Path, state: &str, seq: u64, completed: u64) -> Result<(), String> { + write_json_pretty( + &root.join("daemon-status.json"), + &json!({ + "controller": "panda-rpki-daemon-v1", "state": state, + "pid": std::process::id(), "run_seq": seq, "runs_completed": completed, "updated_at": now(), + }), + ) +} + +pub(crate) fn run(args: &[String]) -> Result<(), String> { + if args.iter().any(|a| a == "--help" || a == "-h") { + return Err(crate::cli::usage()); + } + let mut config = parse(args)?; + crate::logging::configure(&config.log_level, &config.log_format)?; + regular_directory(&config.root)?; + config.root = fs::canonicalize(&config.root).map_err(|e| e.to_string())?; + let lock = OpenOptions::new() + .create(true) + .truncate(false) + .read(true) + .write(true) + .open(config.root.join("daemon.lock")) + .map_err(|e| e.to_string())?; + lock.try_lock() + .map_err(|e| format!("daemon state-root is locked: {e}"))?; + let _signals = process::Signals::install()?; + let result = controller(&config); + if result.is_err() { + let path = config.root.join("daemon-status.json"); + if let Ok(mut last) = read_json(&path) { + last["state"] = json!("failed"); + last["updated_at"] = json!(now()); + let _ = write_json_pretty(&path, &last); + } + } + result +} + +fn controller(config: &Config) -> Result<(), String> { + let root = &config.root; + let runs = root.join("runs"); + let state = root.join("state"); + regular_directory(&runs)?; + regular_directory(&state)?; + let lifecycle_path = root.join("lifecycle.json"); + let mut lifecycle: Lifecycle = if lifecycle_path.exists() { + serde_json::from_value(read_json(&lifecycle_path)?).map_err(|e| e.to_string())? + } else { + Lifecycle::default() + }; + for entry in fs::read_dir(&runs).map_err(|e| e.to_string())? { + let entry = entry.map_err(|e| e.to_string())?; + if let Some(seq) = run_sequence(&entry.file_name().to_string_lossy()) { + if seq > lifecycle.last_seq { + lifecycle.last_success = false; + } + lifecycle.last_seq = lifecycle.last_seq.max(seq); + } + } + let executable = std::env::current_exe().map_err(|e| e.to_string())?; + let mut completed = 0u64; + let mut failures = 0u64; + status(root, "starting", lifecycle.last_seq, completed)?; + while !process::stopping() && config.max_runs.is_none_or(|n| completed < n) { + let mode = sync_mode(&state, lifecycle.last_success)?; + lifecycle.last_seq = lifecycle + .last_seq + .checked_add(1) + .ok_or("run sequence overflow")?; + lifecycle.last_success = false; + write_json_pretty(&lifecycle_path, &lifecycle)?; + let seq = lifecycle.last_seq; + let out = runs.join(format!("run_{seq:06}")); + fs::create_dir(&out).map_err(|e| format!("create run: {e}"))?; + let started_at = now(); + let start = Instant::now(); + let mut meta = json!({"controller": "panda-rpki-daemon-v1", "run_seq": seq, + "status": "running", "sync_mode": mode, "started_at": started_at}); + write_json_pretty(&out.join("run-meta.json"), &meta)?; + status(root, "running", seq, completed)?; + emit( + Level::Info, + "daemon_run_started", + || json!({"run_seq": seq, "sync_mode": mode}), + ); + // Child-per-run execution and stdout/stderr redirection; paths + // are supplied as arguments, never rendered into shell command text. + let time_path = out.join("process-time.txt"); + let with_time = Path::new("/usr/bin/time").is_file(); + let mut command = if with_time { + let mut command = Command::new("/usr/bin/time"); + command + .args(["-v", "-o"]) + .arg(&time_path) + .arg("--") + .arg(&executable); + command + } else { + Command::new(&executable) + }; + command.env("LC_ALL", "C"); + command + .arg("validate") + .args(&config.child_args) + .arg("--out") + .arg(&out) + .arg("--ccr-out") + .arg(out.join("result.ccr")) + .arg("--rrdp-state-dir") + .arg(&state) + .arg("--rrdp-sync-mode") + .arg(mode) + .stdin(Stdio::null()) + .stdout(File::create(out.join("stdout.log")).map_err(|e| e.to_string())?) + .stderr(File::create(out.join("stderr.log")).map_err(|e| e.to_string())?); + let result = process::execute(&mut command, config.timeout, config.grace); + let (exit_code, failure) = match result { + Ok((exit, reason)) => ( + exit.code(), + reason + .map(str::to_string) + .or_else(|| (!exit.success()).then(|| format!("validator exited: {exit}"))), + ), + Err(error) => (None, Some(error)), + }; + let summary = read_json(&out.join("summary.json")); + let failure = failure.or_else(|| summary.as_ref().err().cloned()); + let success = failure.is_none(); + meta["status"] = json!(if success { "success" } else { "failed" }); + meta["finished_at"] = json!(now()); + meta["wall_ms"] = json!(start.elapsed().as_millis() as u64); + meta["exit_code"] = json!(exit_code); + meta["error"] = json!(failure); + meta["summary"] = summary.unwrap_or(Value::Null); + meta["process_metrics"] = + serde_json::to_value(metrics::collect_process_metrics(with_time, &time_path)) + .map_err(|e| e.to_string())?; + write_json_pretty(&out.join("run-meta.json"), &meta)?; + write_json_pretty(&out.join("run-summary.json"), &meta)?; + append_json_line(&root.join("run-summary.jsonl"), &meta)?; + lifecycle.last_success = success; + write_json_pretty(&lifecycle_path, &lifecycle)?; + completed += 1; + failures += u64::from(!success); + let removed = apply_retention(&runs, config.retain)?; + emit( + if success { Level::Info } else { Level::Warn }, + "daemon_run_completed", + || json!({"run_seq": seq, "success": success, "wall_ms": meta["wall_ms"], "removed_runs": removed}), + ); + if process::stopping() || config.max_runs.is_some_and(|n| completed >= n) { + break; + } + status(root, "sleeping", seq, completed)?; + process::sleep(config.interval); + } + status(root, "exited", lifecycle.last_seq, completed)?; + if failures > 0 { + Err(format!("daemon completed with {failures} failed run(s)")) + } else { + Ok(()) + } +} + +#[cfg(test)] +mod tests; diff --git a/src/daemon/process.rs b/src/daemon/process.rs new file mode 100644 index 0000000..ed88244 --- /dev/null +++ b/src/daemon/process.rs @@ -0,0 +1,104 @@ +//! Unix process groups, timeouts and signal handling. +use std::process::{Child, Command, ExitStatus}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::time::{Duration, Instant}; + +static STOP: AtomicBool = AtomicBool::new(false); +extern "C" fn stop(_: libc::c_int) { + STOP.store(true, Ordering::Relaxed); +} + +pub(super) fn stopping() -> bool { + STOP.load(Ordering::Relaxed) +} + +pub(super) struct Signals(Vec<(libc::c_int, libc::sighandler_t)>); +impl Signals { + #[allow(unsafe_code)] + pub(super) fn install() -> Result { + STOP.store(false, Ordering::Relaxed); + let mut guard = Self(Vec::new()); + for signal in [libc::SIGINT, libc::SIGTERM] { + // SAFETY: handler only writes a lock-free atomic; ABI matches signal(2). + let previous = unsafe { libc::signal(signal, stop as libc::sighandler_t) }; + if previous == libc::SIG_ERR { + return Err(std::io::Error::last_os_error().to_string()); + } + guard.0.push((signal, previous)); + } + Ok(guard) + } +} +impl Drop for Signals { + #[allow(unsafe_code)] + fn drop(&mut self) { + for (signal, handler) in &self.0 { + // SAFETY: restore the exact handler returned by signal(2). + unsafe { libc::signal(*signal, *handler) }; + } + } +} + +struct ChildGuard(Child); +impl ChildGuard { + #[allow(unsafe_code)] + fn signal_group(&self, signal: libc::c_int) { + // SAFETY: child is launched in its own group, with pgid equal to child PID. + unsafe { libc::kill(-(self.0.id() as libc::pid_t), signal) }; + } +} +impl Drop for ChildGuard { + fn drop(&mut self) { + // Reap every child and terminate residual descendants on all error paths. + self.signal_group(libc::SIGKILL); + let _ = self.0.wait(); + } +} + +pub(super) fn execute( + command: &mut Command, + timeout: u64, + grace: u64, +) -> Result<(ExitStatus, Option<&'static str>), String> { + use std::os::unix::process::CommandExt; + command.process_group(0); + let mut child = ChildGuard( + command + .spawn() + .map_err(|e| format!("spawn validator: {e}"))?, + ); + let start = Instant::now(); + let mut stop_at = None; + let mut terminated_at = None; + let mut reason = None; + loop { + if let Some(status) = child + .0 + .try_wait() + .map_err(|e| format!("wait validator: {e}"))? + { + return Ok((status, reason)); + } + if stopping() { + stop_at.get_or_insert_with(Instant::now); + } + let timed_out = timeout > 0 && start.elapsed().as_secs() >= timeout; + let grace_expired = stop_at.is_some_and(|at: Instant| at.elapsed().as_secs() >= grace); + if terminated_at.is_none() && (timed_out || grace_expired) { + reason = Some(if timed_out { "timeout" } else { "interrupted" }); + child.signal_group(libc::SIGTERM); + terminated_at = Some(Instant::now()); + } + if terminated_at.is_some_and(|at| at.elapsed() >= Duration::from_secs(2)) { + child.signal_group(libc::SIGKILL); + } + std::thread::sleep(Duration::from_millis(50)); + } +} + +pub(super) fn sleep(seconds: u64) { + let start = Instant::now(); + while !stopping() && start.elapsed().as_secs() < seconds { + std::thread::sleep(Duration::from_millis(100)); + } +} diff --git a/src/daemon/storage.rs b/src/daemon/storage.rs new file mode 100644 index 0000000..09cb5ba --- /dev/null +++ b/src/daemon/storage.rs @@ -0,0 +1,46 @@ +//! Atomic JSON and append-only history writers. +use serde::Serialize; +use std::fs::{self, OpenOptions}; +use std::io::Write; +use std::path::Path; + +pub(super) fn write_json_pretty(path: &Path, value: &T) -> Result<(), String> { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent) + .map_err(|e| format!("create parent dir failed: {}: {e}", parent.display()))?; + } + let bytes = serde_json::to_vec_pretty(value) + .map_err(|e| format!("serialize json failed: {}: {e}", path.display()))?; + // Atomic write (tmp + rename) so concurrent readers never see a torn file. + let mut tmp_name = path + .file_name() + .map(|name| name.to_os_string()) + .unwrap_or_default(); + tmp_name.push(".tmp"); + let tmp_path = path.with_file_name(tmp_name); + fs::write(&tmp_path, &bytes) + .map_err(|e| format!("write json tmp failed: {}: {e}", tmp_path.display()))?; + fs::rename(&tmp_path, path).map_err(|e| { + format!( + "rename json tmp failed: {} -> {}: {e}", + tmp_path.display(), + path.display() + ) + }) +} + +pub(super) fn append_json_line(path: &Path, value: &T) -> Result<(), String> { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent) + .map_err(|e| format!("create parent dir failed: {}: {e}", parent.display()))?; + } + let mut file = OpenOptions::new() + .create(true) + .append(true) + .open(path) + .map_err(|e| format!("open jsonl failed: {}: {e}", path.display()))?; + serde_json::to_writer(&mut file, value) + .map_err(|e| format!("write jsonl failed: {}: {e}", path.display()))?; + file.write_all(b"\n") + .map_err(|e| format!("flush jsonl failed: {}: {e}", path.display())) +} diff --git a/src/daemon/tests.rs b/src/daemon/tests.rs new file mode 100644 index 0000000..5496bc4 --- /dev/null +++ b/src/daemon/tests.rs @@ -0,0 +1,113 @@ +use super::*; + +fn args(extra: &[&str]) -> Vec { + [ + vec!["--state-root", "data"], + extra.to_vec(), + vec!["--", "--tal", "a.tal", "--ta", "a.cer"], + ] + .concat() + .into_iter() + .map(str::to_owned) + .collect() +} + +#[test] +fn defaults_and_invalid_controller_options() { + let config = parse(&args(&[])).unwrap(); + assert_eq!( + ( + config.interval, + config.max_runs, + config.retain, + config.timeout, + config.grace + ), + (60, None, 10, 0, 30) + ); + for bad in [ + vec!["--max-runs", "0"], + vec!["--retain-runs", "0"], + vec!["--interval-secs", "-1"], + vec!["--run-timeout-secs", "bad"], + vec!["--interval-secs", "2", "--interval-secs", "3"], + ] { + assert!(parse(&args(&bad)).is_err()); + } + let config = parse(&args(&["--interval-secs", "0", "--max-runs", "3"])).unwrap(); + assert_eq!((config.interval, config.max_runs), (0, Some(3))); +} + +#[test] +fn rejects_child_overrides_and_unknown_flags() { + for flag in [ + "--out", + "--rrdp-state-dir", + "--rrdp-sync-mode", + "--ccr-out", + "--unknown", + ] { + let mut argv = args(&[]); + argv.extend([flag.into(), "some-value".into()]); + assert!(parse(&argv).is_err(), "{flag}"); + } + let mut argv = args(&[]); + argv.extend(["--parallel-phase2-object-workers".into(), "0".into()]); + assert!(parse(&argv).is_err()); +} + +#[test] +fn protocol_state_selects_snapshot_delta_and_recovery() { + let root = tempfile::tempdir().unwrap(); + assert_eq!(sync_mode(root.path(), false).unwrap(), "snapshot"); + let db = root.path().join("repository-db"); + fs::create_dir(&db).unwrap(); + assert_eq!(sync_mode(root.path(), true).unwrap(), "snapshot"); + fs::write(db.join("CURRENT"), b"x").unwrap(); + assert_eq!(sync_mode(root.path(), true).unwrap(), "delta"); + assert_eq!(sync_mode(root.path(), false).unwrap(), "auto"); +} + +#[test] +fn retention_preserves_unrelated_incomplete_and_symlinked_runs() { + let root = tempfile::tempdir().unwrap(); + for seq in 1..=3 { + let path = root.path().join(format!("run_{seq:06}")); + fs::create_dir(&path).unwrap(); + write_json_pretty( + &path.join("run-summary.json"), + &json!({ + "controller": "panda-rpki-daemon-v1", "run_seq": seq, "status": "success" + }), + ) + .unwrap(); + } + fs::create_dir(root.path().join("unrelated")).unwrap(); + fs::create_dir(root.path().join("run_000004")).unwrap(); + let outside = tempfile::tempdir().unwrap(); + std::os::unix::fs::symlink(outside.path(), root.path().join("run_000005")).unwrap(); + assert_eq!( + apply_retention(root.path(), 1).unwrap(), + ["run_000001", "run_000002"] + ); + for name in ["run_000003", "run_000004", "run_000005", "unrelated"] { + assert!(root.path().join(name).exists()); + } + assert!(regular_directory(&root.path().join("run_000005")).is_err()); +} + +#[test] +fn atomic_json_writers_and_state_lock() { + let root = tempfile::tempdir().unwrap(); + let path = root.path().join("state.json"); + write_json_pretty(&path, &json!({"seq": 1})).unwrap(); + write_json_pretty(&path, &json!({"seq": 2})).unwrap(); + assert_eq!(read_json(&path).unwrap()["seq"], 2); + let lines = root.path().join("summary.jsonl"); + append_json_line(&lines, &json!({"seq": 1})).unwrap(); + append_json_line(&lines, &json!({"seq": 2})).unwrap(); + assert_eq!(fs::read_to_string(lines).unwrap().lines().count(), 2); + let file = File::open(&path).unwrap(); + file.try_lock().unwrap(); + assert!(File::open(path).unwrap().try_lock().is_err()); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9672e64 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,23 @@ +//! RFC-scoped RPKI validation core. +//! +//! The supported integration surface is the validation CLI. Implementation +//! modules are grouped by repository, scheduling, validation, and output. + +#![warn(unsafe_code)] + +pub mod ccr; +pub mod cli; +mod daemon; +pub mod logging; +pub mod model; +pub mod output; +pub mod repository; +pub mod runtime; +pub mod scheduler; +pub mod ta_constraints; +pub mod validation; + +#[cfg(test)] +pub(crate) mod test_support; + +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/src/logging.rs b/src/logging.rs new file mode 100644 index 0000000..d35ace8 --- /dev/null +++ b/src/logging.rs @@ -0,0 +1,335 @@ +//! Process-wide diagnostic output. Validation artifacts never use this sink. +use std::io::Write; +use std::sync::Mutex; +use std::sync::atomic::{AtomicU8, Ordering}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum Level { + Error = 1, + Warn = 2, + Info = 3, + Debug = 4, + Trace = 5, +} + +static FILTER: AtomicU8 = AtomicU8::new(Level::Info as u8); +static JSON: AtomicU8 = AtomicU8::new(0); +static OUTPUT_LOCK: Mutex<()> = Mutex::new(()); + +pub fn parse_level(value: &str) -> Result { + match value { + "off" => Ok(0), + "error" => Ok(1), + "warn" => Ok(2), + "info" => Ok(3), + "debug" => Ok(4), + "trace" => Ok(5), + _ => Err(format!( + "invalid log level: {value}; expected off|error|warn|info|debug|trace" + )), + } +} + +pub fn configure(level: &str, format: &str) -> Result<(), String> { + let filter = parse_level(level)?; + let json = match format { + "text" => 0, + "json" => 1, + _ => return Err(format!("invalid log format: {format}; expected text|json")), + }; + FILTER.store(filter, Ordering::Relaxed); + JSON.store(json, Ordering::Relaxed); + Ok(()) +} + +pub fn enabled(level: Level) -> bool { + level as u8 <= FILTER.load(Ordering::Relaxed) +} + +fn redact_text(text: &str) -> String { + let mut output = String::with_capacity(text.len()); + let lower = text.to_ascii_lowercase(); + let mut cursor = 0; + while cursor < text.len() { + let Some(relative_start) = ["https://", "http://", "rsync://"] + .iter() + .filter_map(|scheme| lower[cursor..].find(scheme).map(|start| (start, *scheme))) + .min_by_key(|(start, _)| *start) + .map(|(start, _)| start) + else { + output.push_str(&text[cursor..]); + break; + }; + let start = cursor + relative_start; + output.push_str(&text[cursor..start]); + let token_end = text[start..] + .find(char::is_whitespace) + .map(|offset| start + offset) + .unwrap_or(text.len()); + let raw = &text[start..token_end]; + let core = raw.trim_end_matches(|ch: char| ",.;:!?)]}'\"".contains(ch)); + let suffix = &raw[core.len()..]; + let redacted = url::Url::parse(core).map(|mut uri| { + let _ = uri.set_username(""); + let _ = uri.set_password(None); + uri.set_query(None); + uri.set_fragment(None); + uri.to_string() + }); + output.push_str(&redacted.unwrap_or_else(|_| "[redacted-uri]".to_string())); + output.push_str(suffix); + cursor = token_end; + } + // Free-form error strings can contain credentials outside a URL. Prefer + // dropping that text to guessing how much of an opaque credential to keep. + let lower = output.to_ascii_lowercase(); + for name in [ + "password", + "token", + "authorization", + "cookie", + "secret", + "api_key", + ] { + for (start, _) in lower.match_indices(name) { + let suffix = lower[start + name.len()..].trim_start_matches([' ', '\"', '\'']); + if suffix.starts_with('=') || suffix.starts_with(':') { + return "[redacted-sensitive-text]".to_string(); + } + } + } + output +} + +fn redact(value: &mut serde_json::Value) { + match value { + serde_json::Value::String(text) => *text = redact_text(text), + serde_json::Value::Array(items) => items.iter_mut().for_each(redact), + serde_json::Value::Object(fields) => { + for (key, value) in fields { + if ["password", "token", "authorization", "cookie", "secret"] + .iter() + .any(|word| key.to_ascii_lowercase().contains(word)) + { + *value = serde_json::Value::String("[redacted]".into()); + } else { + redact(value); + } + } + } + _ => {} + } +} + +/// Payload creation is lazy, so disabled diagnostics do not allocate JSON. +pub fn emit(level: Level, event: &str, payload: impl FnOnce() -> serde_json::Value) { + let _ = emit_to( + FILTER.load(Ordering::Relaxed), + JSON.load(Ordering::Relaxed) != 0, + level, + event, + payload, + |line| { + let _guard = OUTPUT_LOCK + .lock() + .unwrap_or_else(|poison| poison.into_inner()); + std::io::stderr().lock().write_all(line) + }, + ); +} + +fn emit_to( + filter: u8, + json: bool, + level: Level, + event: &str, + payload: impl FnOnce() -> serde_json::Value, + sink: impl FnOnce(&[u8]) -> std::io::Result<()>, +) -> std::io::Result<()> { + if level as u8 > filter { + return Ok(()); + } + let timestamp = time::OffsetDateTime::now_utc() + .format(&time::format_description::well_known::Rfc3339) + .unwrap_or_default(); + let mut fields = payload(); + redact(&mut fields); + let record = serde_json::json!({ + "timestamp": timestamp, + "level": format!("{level:?}").to_lowercase(), + "event": event, + "fields": fields, + }); + let line = if json { + format!("{record}\n") + } else { + format!( + "{} {} {} {}\n", + timestamp, + record["level"].as_str().unwrap_or(""), + event, + record["fields"] + ) + }; + sink(line.as_bytes()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn mixed_case_urls_and_opaque_credentials_are_redacted() { + assert_eq!( + redact_text("HTTPS://alice:pw@example.test/a?token=x#f"), + "https://example.test/a" + ); + assert_eq!(redact_text("password=opaque"), "[redacted-sensitive-text]"); + assert_eq!( + redact_text("Authorization: Bearer opaque"), + "[redacted-sensitive-text]" + ); + } + + #[test] + fn every_threshold_filters_before_payload_and_sink() { + for filter in 0..=5 { + for level in [ + Level::Error, + Level::Warn, + Level::Info, + Level::Debug, + Level::Trace, + ] { + let called = std::cell::Cell::new(false); + let written = std::cell::Cell::new(false); + emit_to( + filter, + true, + level, + "test", + || { + called.set(true); + serde_json::json!({}) + }, + |_| { + written.set(true); + Ok(()) + }, + ) + .unwrap(); + assert_eq!(called.get(), level as u8 <= filter); + assert_eq!(written.get(), called.get()); + } + } + } + + #[test] + fn sink_failure_is_reported_without_panic_and_text_is_one_line() { + let result = emit_to( + 5, + false, + Level::Error, + "failure", + || serde_json::json!({"message":"one\ntwo"}), + |line| { + assert_eq!(line.iter().filter(|&&c| c == b'\n').count(), 1); + Err(std::io::Error::from(std::io::ErrorKind::BrokenPipe)) + }, + ); + assert_eq!(result.unwrap_err().kind(), std::io::ErrorKind::BrokenPipe); + } + + #[test] + fn concurrent_json_records_are_complete_and_parseable() { + let buffer = Mutex::new(Vec::new()); + std::thread::scope(|scope| { + for worker in 0..8 { + let buffer = &buffer; + scope.spawn(move || { + for seq in 0..100 { + emit_to( + 5, + true, + Level::Info, + "parallel", + || serde_json::json!({"worker":worker,"seq":seq}), + |line| buffer.lock().unwrap().write_all(line), + ) + .unwrap(); + } + }); + } + }); + let data = String::from_utf8(buffer.into_inner().unwrap()).unwrap(); + let records: Vec = data + .lines() + .map(|line| serde_json::from_str(line).unwrap()) + .collect(); + assert_eq!(records.len(), 800); + let ids: std::collections::BTreeSet<_> = records + .iter() + .map(|r| { + ( + r["fields"]["worker"].as_u64().unwrap(), + r["fields"]["seq"].as_u64().unwrap(), + ) + }) + .collect(); + assert_eq!(ids.len(), 800); + } + + #[test] + fn levels_have_threshold_order_and_reject_mistakes() { + for (index, name) in ["off", "error", "warn", "info", "debug", "trace"] + .iter() + .enumerate() + { + assert_eq!(parse_level(name).unwrap(), index as u8); + } + assert!(parse_level("verbose").is_err()); + } + + #[test] + fn diagnostics_redact_uri_credentials_and_nested_secrets() { + let mut value = serde_json::json!({"error": "fetch https://alice:secret@example.test/x?token=private#fragment failed", "nested": [{"authorization": "secret"}]}); + redact(&mut value); + assert_eq!(value["error"], "fetch https://example.test/x failed"); + assert_eq!(value["nested"][0]["authorization"], "[redacted]"); + } + + #[test] + fn diagnostics_redact_punctuated_and_multiple_urls() { + let mut value = serde_json::json!({ + "message": "(https://alice:secret@example.test/a?token=x#f), rsync://bob:pw@example.test/m/;" + }); + redact(&mut value); + assert_eq!( + value["message"], + "(https://example.test/a), rsync://example.test/m/;" + ); + } + + #[test] + fn invalid_configuration_does_not_change_filter() { + let before = FILTER.load(Ordering::Relaxed); + assert!(configure("trace", "xml").is_err()); + assert_eq!(FILTER.load(Ordering::Relaxed), before); + } +} +pub(crate) mod progress; + +macro_rules! info { + ($($args:tt)*) => { + crate::logging::emit(crate::logging::Level::Info, "runtime", || serde_json::json!({"message": format!($($args)*)})) + }; +} +pub(crate) use info; + +macro_rules! warning { + ($($args:tt)*) => { + crate::logging::emit(crate::logging::Level::Warn, "runtime_warning", || serde_json::json!({"message": format!($($args)*)})) + }; +} +pub(crate) use warning; diff --git a/src/logging/progress.rs b/src/logging/progress.rs new file mode 100644 index 0000000..e65125c --- /dev/null +++ b/src/logging/progress.rs @@ -0,0 +1,72 @@ +// Event sites construct detailed payloads lazily. +macro_rules! emit { + ($kind:expr, $payload:expr $(,)?) => { + crate::logging::emit(crate::logging::progress::event_level($kind), $kind, || { + $payload + }) + }; +} +pub(crate) use emit; + +pub(crate) fn event_level(kind: &str) -> crate::logging::Level { + if matches!(kind, "http_fetch_failed" | "rrdp_sync_failed") { + crate::logging::Level::Warn + } else if kind == "validation_failed" || kind.ends_with("_failed") || kind.starts_with("error_") + { + crate::logging::Level::Error + } else if kind.ends_with("_warning") + || kind.ends_with("_warnings") + || kind.starts_with("warning_") + { + crate::logging::Level::Warn + } else if kind.starts_with("publication_point") + || kind.starts_with("phase2_") + || kind.starts_with("object_") + { + crate::logging::Level::Trace + } else if kind.starts_with("validation_") + || kind.starts_with("run_") + || kind.starts_with("output_") + || kind.starts_with("ccr_") + || kind.ends_with("_summary") + { + crate::logging::Level::Info + } else { + crate::logging::Level::Debug + } +} + +pub fn slow_threshold_secs() -> f64 { + 30.0 +} + +pub fn stage_fresh_slow_threshold_ms() -> u64 { + 1_000 +} + +pub fn pp_control_slow_threshold_ms() -> u64 { + 100 +} + +pub fn control_loop_slow_threshold_ms() -> u64 { + 1_000 +} + +#[cfg(test)] +mod tests { + use super::event_level; + use crate::logging::Level; + + #[test] + fn event_levels_keep_default_output_quiet() { + assert_eq!(event_level("validation_started"), Level::Info); + assert_eq!(event_level("repository_sync_selected"), Level::Debug); + assert_eq!(event_level("publication_point_object"), Level::Trace); + assert_eq!(event_level("validation_warning"), Level::Warn); + assert_eq!(event_level("validation_warnings"), Level::Warn); + assert_eq!(event_level("validation_failed"), Level::Error); + assert_eq!(event_level("object_failed"), Level::Error); + assert_eq!(event_level("http_fetch_failed"), Level::Warn); + assert_eq!(event_level("rrdp_sync_failed"), Level::Warn); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..d41c302 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,16 @@ +fn main() { + match panda_rpki::cli::run(std::env::args().skip(1)) { + Ok(()) => {} + Err(error) if error == panda_rpki::cli::usage() => { + println!("{error}"); + } + Err(error) => { + panda_rpki::logging::emit( + panda_rpki::logging::Level::Error, + "command_failed", + || serde_json::json!({"error": error}), + ); + std::process::exit(2); + } + } +} diff --git a/src/model/aspa.rs b/src/model/aspa.rs new file mode 100644 index 0000000..685d3b3 --- /dev/null +++ b/src/model/aspa.rs @@ -0,0 +1,412 @@ +use crate::model::common::{DerReader, der_take_tlv}; +use crate::model::oid::OID_CT_ASPA; +use crate::model::rc::ResourceCertificate; +use crate::model::signed_object::{ + RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectDecodeError, SignedObjectParseError, + SignedObjectValidateError, +}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AspaObject { + pub signed_object: RpkiSignedObject, + pub econtent_type: String, + pub aspa: AspaEContent, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AspaObjectParsed { + pub signed_object: RpkiSignedObjectParsed, + pub econtent_type: String, + pub aspa: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AspaEContent { + pub version: u32, + pub customer_as_id: u32, + pub provider_as_ids: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AspaEContentParsed { + der: Vec, +} + +#[derive(Debug, thiserror::Error)] +pub enum AspaParseError { + #[error("signed object parse error: {0} (RFC 6488 §2-§3; RFC 9589 §4)")] + SignedObject(#[from] SignedObjectParseError), + #[error("ASPA parse error: {0} (draft-ietf-sidrops-aspa-profile-21 §3; DER)")] + Parse(String), + + #[error("ASPA trailing bytes: {0} bytes (draft-ietf-sidrops-aspa-profile-21 §3; DER)")] + TrailingBytes(usize), +} + +#[derive(Debug, thiserror::Error)] +pub enum AspaProfileError { + #[error("signed object profile error: {0} (RFC 6488 §2-§3; RFC 9589 §4)")] + SignedObject(#[from] SignedObjectValidateError), + + #[error( + "ASPA eContentType must be {OID_CT_ASPA}, got {0} (draft-ietf-sidrops-aspa-profile-21 §2)" + )] + InvalidEContentType(String), + + #[error("ASPA profile decode error: {0} (draft-ietf-sidrops-aspa-profile-21 §3; DER)")] + ProfileDecode(String), + + #[error( + "ASProviderAttestation must be a SEQUENCE of 3 elements (draft-ietf-sidrops-aspa-profile-21 §3)" + )] + InvalidAttestationSequence, + + #[error( + "ASPA version must be 1 and MUST be explicitly encoded (draft-ietf-sidrops-aspa-profile-21 §3.1)" + )] + VersionMustBeExplicitOne, + + #[error( + "ASPA customerASID out of range (0..=4294967295), got {0} (draft-ietf-sidrops-aspa-profile-21 §3.2)" + )] + CustomerAsIdOutOfRange(u64), + + #[error( + "ASPA providers must contain at least one ASID (draft-ietf-sidrops-aspa-profile-21 §3.3)" + )] + EmptyProviders, + + #[error( + "ASPA provider ASID out of range (0..=4294967295), got {0} (draft-ietf-sidrops-aspa-profile-21 §3.3)" + )] + ProviderAsIdOutOfRange(u64), + + #[error( + "ASPA providers must be in strictly increasing order (draft-ietf-sidrops-aspa-profile-21 §3.3)" + )] + ProvidersNotStrictlyIncreasing, + + #[error( + "ASPA providers contains the customerASID ({0}) which is not allowed (draft-ietf-sidrops-aspa-profile-21 §3.3)" + )] + ProvidersContainCustomer(u32), +} + +impl From for AspaProfileError { + fn from(value: SignedObjectDecodeError) -> Self { + match value { + SignedObjectDecodeError::Parse(e) => AspaProfileError::ProfileDecode(e.to_string()), + SignedObjectDecodeError::Validate(e) => AspaProfileError::SignedObject(e), + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum AspaDecodeError { + #[error("{0}")] + Parse(#[from] AspaParseError), + + #[error("{0}")] + Validate(#[from] AspaProfileError), +} + +#[derive(Debug, thiserror::Error)] +pub enum AspaValidateError { + #[error( + "ASPA EE certificate must contain AS resources extension (draft-ietf-sidrops-aspa-profile-21 §4; RFC 3779 §3.2)" + )] + EeAsResourcesMissing, + + #[error( + "ASPA EE certificate AS resources must not use inherit (draft-ietf-sidrops-aspa-profile-21 §4; RFC 3779 §3.2.3.3)" + )] + EeAsResourcesInherit, + + #[error( + "ASPA EE certificate AS resources must not include ranges (draft-ietf-sidrops-aspa-profile-21 §4; RFC 3779 §3.2.3.6-§3.2.3.7)" + )] + EeAsResourcesRangePresent, + + #[error( + "ASPA EE certificate AS resources must not include RDI (draft-ietf-sidrops-aspa-profile-21 §4; RFC 3779 §3.2.3.5; RFC 6487 §4.8.11)" + )] + EeAsResourcesRdiPresent, + + #[error( + "ASPA EE certificate AS resources must contain exactly one ASID (id element) (draft-ietf-sidrops-aspa-profile-21 §4)" + )] + EeAsResourcesNotSingleId, + + #[error( + "ASPA customerASID ({customer_as_id}) does not match EE AS resources ({ee_as_id}) (draft-ietf-sidrops-aspa-profile-21 §4)" + )] + CustomerAsIdMismatch { customer_as_id: u32, ee_as_id: u32 }, + + #[error( + "ASPA EE certificate must not contain IP resources extension (draft-ietf-sidrops-aspa-profile-21 §4; RFC 3779 §2.2)" + )] + EeIpResourcesPresent, +} + +impl AspaObject { + /// Parse step of scheme A (`parse → validate → verify`). + pub fn parse_der(der: &[u8]) -> Result { + let signed_object = RpkiSignedObject::parse_der(der)?; + let econtent_type = signed_object + .signed_data + .encap_content_info + .econtent_type + .clone(); + let aspa = signed_object + .signed_data + .encap_content_info + .econtent + .as_deref() + .map(AspaEContent::parse_der) + .transpose()?; + Ok(AspaObjectParsed { + signed_object, + econtent_type, + aspa, + }) + } + + /// Profile validate step of scheme A (`parse → validate → verify`). + /// + /// `AspaObject` is already profile-validated when constructed via `decode_der()` / + /// `AspaObjectParsed::validate_profile()`. + pub fn validate_profile(&self) -> Result<(), AspaProfileError> { + Ok(()) + } + + pub fn decode_der(der: &[u8]) -> Result { + Ok(Self::parse_der(der)?.validate_profile()?) + } + + pub fn decode_der_with_strict_options( + der: &[u8], + strict_cms_der: bool, + strict_name: bool, + ) -> Result { + let signed_object = + RpkiSignedObject::decode_der_with_strict_options(der, strict_cms_der, strict_name) + .map_err(AspaProfileError::from)?; + Self::from_signed_object(signed_object) + } + + pub fn from_signed_object(signed_object: RpkiSignedObject) -> Result { + let econtent_type = signed_object + .signed_data + .encap_content_info + .econtent_type + .clone(); + if econtent_type != OID_CT_ASPA { + return Err(AspaProfileError::InvalidEContentType(econtent_type).into()); + } + + let aspa = + AspaEContent::decode_der(&signed_object.signed_data.encap_content_info.econtent)?; + Ok(Self { + aspa, + signed_object, + econtent_type: OID_CT_ASPA.to_string(), + }) + } + + /// Validate this ASPA's embedded EE certificate resources. + pub fn validate_embedded_ee_cert(&self) -> Result<(), AspaValidateError> { + let ee = &self.signed_object.signed_data.certificates[0].resource_cert; + self.aspa.validate_against_ee_cert(ee) + } +} + +impl AspaEContent { + /// Parse step of scheme A (`parse → validate → verify`). + pub fn parse_der(der: &[u8]) -> Result { + let (_tag, _value, rem) = der_take_tlv(der).map_err(AspaParseError::Parse)?; + if !rem.is_empty() { + return Err(AspaParseError::TrailingBytes(rem.len())); + } + Ok(AspaEContentParsed { der: der.to_vec() }) + } + + /// Profile validate step of scheme A (`parse → validate → verify`). + /// + /// `AspaEContent` is already profile-validated when constructed via `decode_der()` / + /// `AspaEContentParsed::validate_profile()`. + pub fn validate_profile(&self) -> Result<(), AspaProfileError> { + Ok(()) + } + + /// Decode the DER-encoded ASProviderAttestation defined in + /// draft-ietf-sidrops-aspa-profile-21 §3 (`parse + validate`). + pub fn decode_der(der: &[u8]) -> Result { + Ok(Self::parse_der(der)?.validate_profile()?) + } + + /// Validate ASPA payload against the embedded EE resource certificate. + /// + /// This implements the EE/payload semantic checks described in + /// `draft-ietf-sidrops-aspa-profile-21` §4 (as summarized in `rpki/specs/08_aspa.md`). + pub fn validate_against_ee_cert( + &self, + ee: &ResourceCertificate, + ) -> Result<(), AspaValidateError> { + if ee.tbs.extensions.ip_resources.is_some() { + return Err(AspaValidateError::EeIpResourcesPresent); + } + + let asn = ee + .tbs + .extensions + .as_resources + .as_ref() + .ok_or(AspaValidateError::EeAsResourcesMissing)?; + + if asn.rdi.is_some() { + return Err(AspaValidateError::EeAsResourcesRdiPresent); + } + if asn.is_asnum_inherit() { + return Err(AspaValidateError::EeAsResourcesInherit); + } + if asn.has_any_range() { + return Err(AspaValidateError::EeAsResourcesRangePresent); + } + + let ee_as_id = asn + .asnum_single_id() + .ok_or(AspaValidateError::EeAsResourcesNotSingleId)?; + if ee_as_id != self.customer_as_id { + return Err(AspaValidateError::CustomerAsIdMismatch { + customer_as_id: self.customer_as_id, + ee_as_id, + }); + } + + Ok(()) + } +} + +impl AspaObjectParsed { + pub fn validate_profile(self) -> Result { + let signed_object = self.signed_object.validate_profile()?; + let econtent_type = signed_object + .signed_data + .encap_content_info + .econtent_type + .clone(); + if econtent_type != OID_CT_ASPA { + return Err(AspaProfileError::InvalidEContentType(econtent_type)); + } + let aspa = self + .aspa + .ok_or_else(|| AspaProfileError::ProfileDecode("ASPA.eContent missing".into()))? + .validate_profile()?; + Ok(AspaObject { + signed_object, + econtent_type: OID_CT_ASPA.to_string(), + aspa, + }) + } +} + +impl AspaEContentParsed { + pub fn validate_profile(self) -> Result { + fn count_elements(mut r: DerReader<'_>) -> Result { + let mut n = 0usize; + while !r.is_empty() { + r.skip_any()?; + n += 1; + } + Ok(n) + } + + let mut r = DerReader::new(&self.der); + let mut seq = r.take_sequence().map_err(AspaProfileError::ProfileDecode)?; + if !r.is_empty() { + return Err(AspaProfileError::ProfileDecode( + "trailing bytes after ASProviderAttestation".into(), + )); + } + + let elem_count = + count_elements(seq).map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?; + if elem_count != 3 { + return Err(AspaProfileError::InvalidAttestationSequence); + } + + // version [0] EXPLICIT INTEGER MUST be present and MUST be 1. + if seq + .peek_tag() + .map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))? + != 0xA0 + { + return Err(AspaProfileError::VersionMustBeExplicitOne); + } + let (inner_tag, inner_val) = seq + .take_explicit(0xA0) + .map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?; + if inner_tag != 0x02 { + return Err(AspaProfileError::VersionMustBeExplicitOne); + } + let v = crate::model::common::der_uint_from_bytes(inner_val) + .map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?; + if v != 1 { + return Err(AspaProfileError::VersionMustBeExplicitOne); + } + + let customer_u64 = seq + .take_uint_u64() + .map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?; + if customer_u64 > u32::MAX as u64 { + return Err(AspaProfileError::CustomerAsIdOutOfRange(customer_u64)); + } + let customer_as_id = customer_u64 as u32; + + let providers_seq = seq + .take_sequence() + .map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?; + if !seq.is_empty() { + return Err(AspaProfileError::InvalidAttestationSequence); + } + let providers = parse_providers_cursor(providers_seq, customer_as_id)?; + + Ok(AspaEContent { + version: 1, + customer_as_id, + provider_as_ids: providers, + }) + } +} + +fn parse_providers_cursor( + mut seq: DerReader<'_>, + customer_as_id: u32, +) -> Result, AspaProfileError> { + if seq.is_empty() { + return Err(AspaProfileError::EmptyProviders); + } + + let mut out: Vec = Vec::new(); + let mut prev: Option = None; + while !seq.is_empty() { + let v = seq + .take_uint_u64() + .map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?; + if v > u32::MAX as u64 { + return Err(AspaProfileError::ProviderAsIdOutOfRange(v)); + } + let asn = v as u32; + if asn == customer_as_id { + return Err(AspaProfileError::ProvidersContainCustomer(customer_as_id)); + } + if let Some(p) = prev + && asn <= p + { + return Err(AspaProfileError::ProvidersNotStrictlyIncreasing); + } + prev = Some(asn); + out.push(asn); + } + Ok(out) +} diff --git a/src/model/common.rs b/src/model/common.rs new file mode 100644 index 0000000..b654c45 --- /dev/null +++ b/src/model/common.rs @@ -0,0 +1,289 @@ +use x509_parser::asn1_rs::Tag; +use x509_parser::prelude::FromDer; +use x509_parser::x509::AlgorithmIdentifier; + +pub type UtcTime = time::OffsetDateTime; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Asn1TimeEncoding { + UtcTime, + GeneralizedTime, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Asn1TimeUtc { + pub utc: UtcTime, + pub encoding: Asn1TimeEncoding, +} + +impl Asn1TimeUtc { + /// Validate Time encoding rules (RFC 5280): years 1950-2049 use UTCTime, + /// other years use GeneralizedTime. + pub fn validate_encoding_rfc5280( + &self, + field: &'static str, + ) -> Result<(), InvalidTimeEncodingError> { + let year = self.utc.year(); + let expected = if year <= 2049 { + Asn1TimeEncoding::UtcTime + } else { + Asn1TimeEncoding::GeneralizedTime + }; + if self.encoding != expected { + return Err(InvalidTimeEncodingError { + field, + year, + encoding: self.encoding, + }); + } + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BigUnsigned { + /// Minimal big-endian bytes. For zero, this is `[0]`. + pub bytes_be: Vec, +} + +impl BigUnsigned { + pub fn from_biguint(n: &der_parser::num_bigint::BigUint) -> Self { + let mut bytes = n.to_bytes_be(); + if bytes.is_empty() { + bytes.push(0); + } + Self { bytes_be: bytes } + } + + pub fn to_hex_upper(&self) -> String { + hex::encode_upper(&self.bytes_be) + } + + pub fn to_u64(&self) -> Option { + if self.bytes_be.len() > 8 { + return None; + } + let mut value: u64 = 0; + for &b in &self.bytes_be { + value = (value << 8) | (b as u64); + } + Some(value) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)] +#[error( + "{field} time encoding invalid for year {year}: got {encoding:?} (RFC 5280 §4.1.2.5; RFC 5280 §5.1.2.4-§5.1.2.6)" +)] +pub struct InvalidTimeEncodingError { + pub field: &'static str, + pub year: i32, + pub encoding: Asn1TimeEncoding, +} + +pub fn asn1_time_to_model(t: x509_parser::time::ASN1Time) -> Asn1TimeUtc { + let encoding = if t.is_utctime() { + Asn1TimeEncoding::UtcTime + } else { + Asn1TimeEncoding::GeneralizedTime + }; + Asn1TimeUtc { + utc: t.to_datetime(), + encoding, + } +} + +pub fn algorithm_params_absent_or_null(sig: &AlgorithmIdentifier<'_>) -> bool { + match sig.parameters.as_ref() { + None => true, + Some(p) if p.tag() == Tag::Null => true, + Some(_p) => false, + } +} + +/// Take a single DER TLV (Tag-Length-Value) from the start of `input`. +/// +/// This helper supports: +/// - short- and long-form lengths (up to 8 length bytes) +/// - only low-tag-number form tags (no high-tag-number form) +/// - definite length only (DER forbids indefinite length) +/// +/// Returns: `(tag_byte, value_bytes, remaining_bytes)`. +pub(crate) fn der_take_tlv(input: &[u8]) -> Result<(u8, &[u8], &[u8]), String> { + if input.len() < 2 { + return Err("truncated DER (need tag+len)".into()); + } + let tag = input[0]; + if (tag & 0x1F) == 0x1F { + return Err("high-tag-number form not supported".into()); + } + let len0 = input[1]; + if len0 == 0x80 { + return Err("indefinite length not allowed in DER".into()); + } + let (len, hdr_len) = if len0 & 0x80 == 0 { + (len0 as usize, 2usize) + } else { + let n = (len0 & 0x7F) as usize; + if n == 0 || n > 8 { + return Err("invalid DER length".into()); + } + if input.len() < 2 + n { + return Err("truncated DER (length bytes)".into()); + } + let mut l: usize = 0; + for &b in &input[2..2 + n] { + l = (l << 8) | (b as usize); + } + (l, 2 + n) + }; + if input.len() < hdr_len + len { + return Err("truncated DER (value bytes)".into()); + } + let value = &input[hdr_len..hdr_len + len]; + let rem = &input[hdr_len + len..]; + Ok((tag, value, rem)) +} + +/// Minimal streaming DER reader built on `der_take_tlv`. +/// +/// This is intentionally small and only supports the subset of DER needed by +/// RPKI object eContent decoders (ROA/ASPA), to avoid constructing a generic AST +/// (which is expensive on large objects such as ROAs with thousands of prefixes). +#[derive(Clone, Copy)] +pub(crate) struct DerReader<'a> { + buf: &'a [u8], +} + +impl<'a> DerReader<'a> { + pub(crate) fn new(buf: &'a [u8]) -> Self { + Self { buf } + } + + pub(crate) fn is_empty(&self) -> bool { + self.buf.is_empty() + } + + pub(crate) fn peek_tag(&self) -> Result { + self.buf + .first() + .copied() + .ok_or_else(|| "truncated DER".into()) + } + + pub(crate) fn take_any(&mut self) -> Result<(u8, &'a [u8]), String> { + let (tag, value, rem) = der_take_tlv(self.buf)?; + self.buf = rem; + Ok((tag, value)) + } + + pub(crate) fn take_any_full(&mut self) -> Result<(u8, &'a [u8], &'a [u8]), String> { + let (tag, value, rem) = der_take_tlv(self.buf)?; + let consumed = self.buf.len() - rem.len(); + let full = &self.buf[..consumed]; + self.buf = rem; + Ok((tag, full, value)) + } + + pub(crate) fn skip_any(&mut self) -> Result<(), String> { + let _ = self.take_any()?; + Ok(()) + } + + pub(crate) fn take_tag(&mut self, expected_tag: u8) -> Result<&'a [u8], String> { + let (tag, value) = self.take_any()?; + if tag != expected_tag { + return Err(format!( + "unexpected tag: got 0x{tag:02X}, expected 0x{expected_tag:02X}" + )); + } + Ok(value) + } + + pub(crate) fn take_sequence(&mut self) -> Result, String> { + let value = self.take_tag(0x30)?; + Ok(DerReader::new(value)) + } + + pub(crate) fn take_octet_string(&mut self) -> Result<&'a [u8], String> { + self.take_tag(0x04) + } + + pub(crate) fn take_bit_string(&mut self) -> Result<(u8, &'a [u8]), String> { + let v = self.take_tag(0x03)?; + if v.is_empty() { + return Err("BIT STRING content is empty".into()); + } + Ok((v[0], &v[1..])) + } + + pub(crate) fn take_uint_u64(&mut self) -> Result { + let v = self.take_tag(0x02)?; + der_uint_from_bytes(v) + } + + pub(crate) fn take_explicit( + &mut self, + expected_outer_tag: u8, + ) -> Result<(u8, &'a [u8]), String> { + let inner_der = self.take_tag(expected_outer_tag)?; + let (tag, value, rem) = der_take_tlv(inner_der)?; + if !rem.is_empty() { + return Err("trailing bytes inside EXPLICIT value".into()); + } + Ok((tag, value)) + } +} + +pub(crate) fn der_uint_from_bytes(bytes: &[u8]) -> Result { + if bytes.is_empty() { + return Err("INTEGER has empty content".into()); + } + // Disallow negative values. + if (bytes[0] & 0x80) != 0 { + return Err("INTEGER is negative".into()); + } + // DER requires minimal encoding for INTEGER. + if bytes.len() > 1 && bytes[0] == 0x00 && (bytes[1] & 0x80) == 0 { + return Err("INTEGER not minimally encoded".into()); + } + if bytes.len() > 8 { + return Err("INTEGER does not fit u64".into()); + } + let mut v: u64 = 0; + for &b in bytes { + v = (v << 8) | (b as u64); + } + Ok(v) +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct X509NameDer(pub Vec); + +impl X509NameDer { + pub fn as_raw(&self) -> &[u8] { + &self.0 + } +} + +impl std::fmt::Display for X509NameDer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Ok((rem, name)) = x509_parser::x509::X509Name::from_der(&self.0) else { + return write!(f, ""); + }; + if !rem.is_empty() { + return write!(f, ""); + } + write!(f, "{name}") + } +} + +/// Filename extensions registered in IANA "RPKI Repository Name Schemes". +/// +/// Source: +/// Snapshot date: 2026-01-28. +/// +/// Notes: +/// - Includes entries marked TEMPORARY/DEPRECATED by IANA (e.g., `asa`, `gbr`). +pub const IANA_RPKI_REPOSITORY_FILENAME_EXTENSIONS: &[&str] = + &["asa", "cer", "crl", "gbr", "mft", "roa", "sig", "tak"]; diff --git a/src/model/crl.rs b/src/model/crl.rs new file mode 100644 index 0000000..e1182de --- /dev/null +++ b/src/model/crl.rs @@ -0,0 +1,537 @@ +pub use crate::model::common::{Asn1TimeEncoding, Asn1TimeUtc, BigUnsigned}; +use crate::model::oid::{ + OID_AUTHORITY_KEY_IDENTIFIER, OID_AUTHORITY_KEY_IDENTIFIER_RAW, OID_CRL_NUMBER, + OID_CRL_NUMBER_RAW, OID_SHA256_WITH_RSA_ENCRYPTION, OID_SHA256_WITH_RSA_ENCRYPTION_RAW, + OID_SUBJECT_KEY_IDENTIFIER_RAW, +}; +use x509_parser::certificate::X509Certificate; +use x509_parser::extensions::{ParsedExtension, X509Extension}; +use x509_parser::prelude::{FromDer, X509Version}; +use x509_parser::revocation_list::CertificateRevocationList; +use x509_parser::x509::{AlgorithmIdentifier, SubjectPublicKeyInfo}; +use x509_parser::{asn1_rs::Class as Asn1Class, asn1_rs::Tag as Asn1Tag}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RevokedCert { + pub serial_number: BigUnsigned, + pub revocation_date: Asn1TimeUtc, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CrlExtensions { + pub authority_key_identifier: Vec, + pub crl_number: BigUnsigned, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RpkixCrl { + pub raw_der: Vec, + pub version: u32, + pub issuer_dn: String, + pub signature_algorithm_oid: String, + pub this_update: Asn1TimeUtc, + pub next_update: Asn1TimeUtc, + pub revoked_certs: Vec, + pub extensions: CrlExtensions, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RpkixCrlParsed { + pub raw_der: Vec, + pub version: Option, + pub issuer_dn: String, + pub signature_algorithm: AlgorithmIdentifierValue, + pub tbs_signature_algorithm: AlgorithmIdentifierValue, + pub this_update: Asn1TimeUtc, + pub next_update: Option, + pub revoked_certs: Vec, + pub extensions: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RevokedCertParsed { + pub serial_number: BigUnsigned, + pub revocation_date: Asn1TimeUtc, + pub has_extensions: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum CrlExtensionParsed { + AuthorityKeyIdentifier { + key_identifier: Option>, + has_other_fields: bool, + critical: bool, + }, + CrlNumber { + number: der_parser::num_bigint::BigUint, + critical: bool, + }, + Other { + oid: String, + critical: bool, + }, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AlgorithmIdentifierValue { + pub oid: String, + pub parameters: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AlgorithmParametersValue { + pub class: Asn1Class, + pub tag: Asn1Tag, + pub data: Vec, +} + +impl AlgorithmIdentifierValue { + pub fn params_absent_or_null(&self) -> bool { + match &self.parameters { + None => true, + Some(p) if p.class == Asn1Class::Universal && p.tag == Asn1Tag::Null => true, + Some(_p) => false, + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum CrlParseError { + #[error("X.509 CRL parse error: {0} (RFC 5280 §5.1; RFC 6487 §5)")] + Parse(String), + + #[error("trailing bytes after CRL DER: {0} bytes (DER; RFC 5280 §5.1)")] + TrailingBytes(usize), +} + +#[derive(Debug, thiserror::Error)] +pub enum CrlProfileError { + #[error("CRL version must be v2, got {0:?} (RFC 5280 §5.1; RFC 6487 §5)")] + InvalidVersion(Option), + + #[error("CRL signatureAlgorithm must match TBSCertList.signature (RFC 5280 §5.1)")] + SignatureAlgorithmMismatch, + + #[error( + "CRL signatureAlgorithm must be sha256WithRSAEncryption ({OID_SHA256_WITH_RSA_ENCRYPTION}), got {0} (RFC 6487 §5; RFC 7935 §2)" + )] + InvalidSignatureAlgorithm(String), + + #[error( + "CRL signature algorithm parameters must be absent or NULL (RFC 5280 §4.1.1.2; RFC 7935 §2)" + )] + InvalidSignatureAlgorithmParameters, + + #[error("CRL extensions must be exactly two (AKI + CRLNumber), got {0} (RFC 9829 §3.1)")] + InvalidExtensionsCount(usize), + + #[error("unsupported CRL extension OID {0} (RFC 9829 §3.1)")] + UnsupportedExtension(String), + + #[error("duplicate CRL extension OID {0} (RFC 5280 §4.2; RFC 9829 §3.1)")] + DuplicateExtension(String), + + #[error("AuthorityKeyIdentifier CRL extension missing (RFC 9829 §3.1; RFC 5280 §5.2.1)")] + AkiMissing, + + #[error("AuthorityKeyIdentifier must contain keyIdentifier (RFC 5280 §5.2.1; RFC 9829 §3.1)")] + AkiMissingKeyIdentifier, + + #[error( + "AuthorityKeyIdentifier must not contain authorityCertIssuer or authorityCertSerialNumber (RFC 5280 §5.2.1; RFC 9829 §3.1)" + )] + AkiHasOtherFields, + + #[error("CRLNumber CRL extension missing (RFC 9829 §3.1; RFC 5280 §5.2.3)")] + CrlNumberMissing, + + #[error("CRLNumber must be non-critical (RFC 9829 §3.1; RFC 5280 §5.2.3)")] + CrlNumberCritical, + + #[error("CRLNumber out of range (must fit in 0..2^159-1) (RFC 9829 §3.1)")] + CrlNumberOutOfRange, + + #[error("CRL entry extensions must not be present (RFC 6487 §5; RFC 5280 §5.1)")] + EntryExtensionsNotAllowed, + + #[error("CRL nextUpdate must be present (RFC 5280 §5.1.2.5; RFC 6487 §5)")] + NextUpdateMissing, + + #[error( + "{field} time encoding invalid for year {year}: got {encoding:?} (RFC 5280 §5.1.2.4-§5.1.2.6)" + )] + InvalidTimeEncoding { + field: &'static str, + year: i32, + encoding: Asn1TimeEncoding, + }, +} + +#[derive(Debug, thiserror::Error)] +pub enum CrlDecodeError { + #[error("{0}")] + Parse(#[from] CrlParseError), + + #[error("{0}")] + Validate(#[from] CrlProfileError), +} + +impl RpkixCrl { + /// Parse step of scheme A (`parse → validate → verify`). + pub fn parse_der(der: &[u8]) -> Result { + let (rem, crl) = CertificateRevocationList::from_der(der) + .map_err(|e| CrlParseError::Parse(e.to_string()))?; + if !rem.is_empty() { + return Err(CrlParseError::TrailingBytes(rem.len())); + } + + let revoked_certs = crl + .iter_revoked_certificates() + .map(|rc| RevokedCertParsed { + serial_number: BigUnsigned::from_biguint(rc.serial()), + revocation_date: crate::model::common::asn1_time_to_model(rc.revocation_date), + has_extensions: !rc.extensions().is_empty(), + }) + .collect::>(); + + let this_update = crate::model::common::asn1_time_to_model(crl.last_update()); + let next_update = crl + .next_update() + .map(crate::model::common::asn1_time_to_model); + + let extensions = parse_extensions_parse(crl.extensions()).map_err(CrlParseError::Parse)?; + + Ok(RpkixCrlParsed { + raw_der: der.to_vec(), + version: crl.version(), + issuer_dn: crl.issuer().to_string(), + signature_algorithm: algorithm_identifier_value(&crl.signature_algorithm), + tbs_signature_algorithm: algorithm_identifier_value(&crl.tbs_cert_list.signature), + this_update, + next_update, + revoked_certs, + extensions, + }) + } + + /// Decode a DER-encoded X.509 v2 CRL and enforce the RPKI profile constraints from + /// `specs/prepare/data_models/04_crl.md` (RFC 6487 §5; RFC 9829 §3.1; RFC 5280 §5.1). + pub fn decode_der(der: &[u8]) -> Result { + Ok(Self::parse_der(der)?.validate_profile()?) + } + + /// Profile validate step of scheme A (`parse → validate → verify`). + /// + /// `RpkixCrl` is already profile-validated when constructed via `decode_der()` / + /// `RpkixCrlParsed::validate_profile()`. + pub fn validate_profile(&self) -> Result<(), CrlProfileError> { + Ok(()) + } + + /// Verify the cryptographic signature on this CRL using the issuer certificate. + /// + /// Signature verification needs the issuer public key (RFC 5280 §6.3.3 (f)-(g)). + /// In RPKI practice, this public key is obtained from the CRL issuer CA certificate + /// (and that certificate must already be validated up to the same trust anchor). + /// + /// This helper also performs common binding checks: + /// - CRL `issuer_dn` must equal issuer certificate `subject` + /// - if issuer KeyUsage is present, require `cRLSign` + /// - if issuer SKI is present, require it matches CRL AKI.keyIdentifier + pub fn verify_signature_with_issuer_certificate_der( + &self, + issuer_cert_der: &[u8], + ) -> Result<(), CrlVerifyError> { + let (rem, issuer_cert) = X509Certificate::from_der(issuer_cert_der) + .map_err(|e| CrlVerifyError::IssuerCertificateParse(e.to_string()))?; + if !rem.is_empty() { + return Err(CrlVerifyError::IssuerCertificateTrailingBytes(rem.len())); + } + + let subject_dn = issuer_cert.subject().to_string(); + if subject_dn != self.issuer_dn { + return Err(CrlVerifyError::IssuerSubjectMismatch { + crl_issuer_dn: self.issuer_dn.clone(), + issuer_subject_dn: subject_dn, + }); + } + + if let Some(ku) = issuer_cert + .key_usage() + .map_err(|e| CrlVerifyError::IssuerCertificateParse(e.to_string()))? + && !ku.value.crl_sign() + { + return Err(CrlVerifyError::IssuerKeyUsageMissingCrlSign); + } + + if let Some(issuer_ski) = get_subject_key_identifier(&issuer_cert) + && issuer_ski != self.extensions.authority_key_identifier + { + return Err(CrlVerifyError::AkiSkiMismatch); + } + + self.verify_signature_with_issuer_spki(issuer_cert.public_key()) + } + + /// Verify the cryptographic signature on this CRL using the issuer SubjectPublicKeyInfo. + pub fn verify_signature_with_issuer_spki( + &self, + issuer_spki: &SubjectPublicKeyInfo<'_>, + ) -> Result<(), CrlVerifyError> { + let (rem, crl) = CertificateRevocationList::from_der(&self.raw_der) + .map_err(|e| CrlVerifyError::CrlParse(e.to_string()))?; + if !rem.is_empty() { + return Err(CrlVerifyError::CrlTrailingBytes(rem.len())); + } + + crl.verify_signature(issuer_spki) + .map_err(|e| CrlVerifyError::InvalidSignature(e.to_string())) + } + + /// Verify the cryptographic signature on this CRL using a DER-encoded SubjectPublicKeyInfo. + pub fn verify_signature_with_issuer_spki_der( + &self, + issuer_spki_der: &[u8], + ) -> Result<(), CrlVerifyError> { + let (rem, spki) = SubjectPublicKeyInfo::from_der(issuer_spki_der) + .map_err(|e| CrlVerifyError::IssuerSpkiParse(e.to_string()))?; + if !rem.is_empty() { + return Err(CrlVerifyError::IssuerSpkiTrailingBytes(rem.len())); + } + self.verify_signature_with_issuer_spki(&spki) + } +} + +impl RpkixCrlParsed { + /// Profile validate step of scheme A (`parse → validate → verify`). + pub fn validate_profile(self) -> Result { + let version = match self.version { + Some(X509Version::V2) => 2, + Some(v) => return Err(CrlProfileError::InvalidVersion(Some(v.0))), + None => return Err(CrlProfileError::InvalidVersion(None)), + }; + + // signatureAlgorithm must match tbsCertList.signature + if self.signature_algorithm != self.tbs_signature_algorithm { + return Err(CrlProfileError::SignatureAlgorithmMismatch); + } + let sig_oid = self.signature_algorithm.oid.clone(); + if sig_oid != OID_SHA256_WITH_RSA_ENCRYPTION { + return Err(CrlProfileError::InvalidSignatureAlgorithm(sig_oid)); + } + if !self.signature_algorithm.params_absent_or_null() { + return Err(CrlProfileError::InvalidSignatureAlgorithmParameters); + } + + let extensions = validate_extensions_profile(&self.extensions)?; + + let mut revoked_out = Vec::with_capacity(self.revoked_certs.len()); + for rc in self.revoked_certs { + if rc.has_extensions { + return Err(CrlProfileError::EntryExtensionsNotAllowed); + } + validate_time_encoding_rfc5280("revocationDate", &rc.revocation_date)?; + revoked_out.push(RevokedCert { + serial_number: rc.serial_number, + revocation_date: rc.revocation_date, + }); + } + + validate_time_encoding_rfc5280("thisUpdate", &self.this_update)?; + + let next_update = self.next_update.ok_or(CrlProfileError::NextUpdateMissing)?; + validate_time_encoding_rfc5280("nextUpdate", &next_update)?; + + Ok(RpkixCrl { + raw_der: self.raw_der, + version, + issuer_dn: self.issuer_dn, + signature_algorithm_oid: OID_SHA256_WITH_RSA_ENCRYPTION.to_string(), + this_update: self.this_update, + next_update, + revoked_certs: revoked_out, + extensions, + }) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum CrlVerifyError { + #[error("issuer certificate parse error: {0} (RFC 5280 §4.1; RFC 6487 §4)")] + IssuerCertificateParse(String), + + #[error("trailing bytes after issuer certificate DER: {0} bytes (DER; RFC 5280 §4.1)")] + IssuerCertificateTrailingBytes(usize), + + #[error("issuer SubjectPublicKeyInfo parse error: {0} (RFC 5280 §4.1.2.7)")] + IssuerSpkiParse(String), + + #[error( + "trailing bytes after issuer SubjectPublicKeyInfo DER: {0} bytes (DER; RFC 5280 §4.1.2.7)" + )] + IssuerSpkiTrailingBytes(usize), + + #[error("CRL parse error: {0} (RFC 5280 §5.1; RFC 6487 §5)")] + CrlParse(String), + + #[error("trailing bytes after CRL DER: {0} bytes (DER; RFC 5280 §5.1)")] + CrlTrailingBytes(usize), + + #[error( + "CRL issuer DN does not match issuer certificate subject (RFC 5280 §5.1; RFC 5280 §6.3.3(b))" + )] + IssuerSubjectMismatch { + crl_issuer_dn: String, + issuer_subject_dn: String, + }, + + #[error( + "issuer certificate keyUsage present but missing cRLSign (RFC 5280 §4.2.1.3; RFC 5280 §6.3.3(f))" + )] + IssuerKeyUsageMissingCrlSign, + + #[error( + "CRL AKI.keyIdentifier does not match issuer certificate SKI (RFC 5280 §4.2.1.1; RFC 5280 §4.2.1.2; RFC 5280 §6.3.3(c)/(f))" + )] + AkiSkiMismatch, + + #[error("CRL signature verification failed: {0} (RFC 5280 §6.3.3(g); RFC 7935 §2)")] + InvalidSignature(String), +} + +fn validate_time_encoding_rfc5280( + field: &'static str, + t: &Asn1TimeUtc, +) -> Result<(), CrlProfileError> { + let year = t.utc.year(); + let expected = if year <= 2049 { + Asn1TimeEncoding::UtcTime + } else { + Asn1TimeEncoding::GeneralizedTime + }; + if t.encoding != expected { + return Err(CrlProfileError::InvalidTimeEncoding { + field, + year, + encoding: t.encoding, + }); + } + Ok(()) +} + +fn algorithm_identifier_value(ai: &AlgorithmIdentifier<'_>) -> AlgorithmIdentifierValue { + let parameters = ai.parameters.as_ref().map(|p| AlgorithmParametersValue { + class: p.class(), + tag: p.tag(), + data: p.as_bytes().to_vec(), + }); + // NOTE(perf): Avoid `to_id_string()` allocations for the signature algorithms we expect + // in RPKI CRLs. Fall back to `to_id_string()` for unexpected algorithms (mostly error paths). + let oid = if ai.algorithm.as_bytes() == OID_SHA256_WITH_RSA_ENCRYPTION_RAW { + OID_SHA256_WITH_RSA_ENCRYPTION.to_string() + } else { + ai.algorithm.to_id_string() + }; + AlgorithmIdentifierValue { oid, parameters } +} + +fn parse_extensions_parse(exts: &[X509Extension<'_>]) -> Result, String> { + let mut out = Vec::with_capacity(exts.len()); + for ext in exts { + let oid = ext.oid.as_bytes(); + if oid == OID_AUTHORITY_KEY_IDENTIFIER_RAW { + let ParsedExtension::AuthorityKeyIdentifier(aki) = ext.parsed_extension() else { + return Err("AKI extension parse failed".to_string()); + }; + out.push(CrlExtensionParsed::AuthorityKeyIdentifier { + key_identifier: aki.key_identifier.as_ref().map(|k| k.0.to_vec()), + has_other_fields: aki.authority_cert_issuer.is_some() + || aki.authority_cert_serial.is_some(), + critical: ext.critical, + }); + } else if oid == OID_CRL_NUMBER_RAW { + match ext.parsed_extension() { + ParsedExtension::CRLNumber(n) => out.push(CrlExtensionParsed::CrlNumber { + number: n.clone(), + critical: ext.critical, + }), + _ => return Err("CRLNumber extension parse failed".to_string()), + } + } else { + out.push(CrlExtensionParsed::Other { + oid: ext.oid.to_id_string(), + critical: ext.critical, + }) + } + } + Ok(out) +} + +fn validate_extensions_profile( + exts: &[CrlExtensionParsed], +) -> Result { + if exts.len() != 2 { + return Err(CrlProfileError::InvalidExtensionsCount(exts.len())); + } + + let mut seen: Vec = Vec::new(); + let mut authority_key_identifier: Option> = None; + let mut crl_number: Option = None; + + for ext in exts { + match ext { + CrlExtensionParsed::AuthorityKeyIdentifier { + key_identifier, + has_other_fields, + critical: _, + } => { + let oid = OID_AUTHORITY_KEY_IDENTIFIER.to_string(); + if seen.iter().any(|s| s == &oid) { + return Err(CrlProfileError::DuplicateExtension(oid)); + } + seen.push(oid.clone()); + + if *has_other_fields { + return Err(CrlProfileError::AkiHasOtherFields); + } + let ki = key_identifier + .as_ref() + .ok_or(CrlProfileError::AkiMissingKeyIdentifier)?; + authority_key_identifier = Some(ki.clone()); + } + CrlExtensionParsed::CrlNumber { number, critical } => { + let oid = OID_CRL_NUMBER.to_string(); + if seen.iter().any(|s| s == &oid) { + return Err(CrlProfileError::DuplicateExtension(oid)); + } + seen.push(oid.clone()); + + if *critical { + return Err(CrlProfileError::CrlNumberCritical); + } + if number.bits() > 159 { + return Err(CrlProfileError::CrlNumberOutOfRange); + } + crl_number = Some(BigUnsigned::from_biguint(number)); + } + CrlExtensionParsed::Other { oid, .. } => { + return Err(CrlProfileError::UnsupportedExtension(oid.clone())); + } + } + } + + Ok(CrlExtensions { + authority_key_identifier: authority_key_identifier.ok_or(CrlProfileError::AkiMissing)?, + crl_number: crl_number.ok_or(CrlProfileError::CrlNumberMissing)?, + }) +} + +fn get_subject_key_identifier(cert: &X509Certificate<'_>) -> Option> { + cert.extensions() + .iter() + .find(|ext| ext.oid.as_bytes() == OID_SUBJECT_KEY_IDENTIFIER_RAW) + .and_then(|ext| match ext.parsed_extension() { + ParsedExtension::SubjectKeyIdentifier(ki) => Some(ki.0.to_vec()), + _ => None, + }) +} diff --git a/src/model/manifest.rs b/src/model/manifest.rs new file mode 100644 index 0000000..18e0fec --- /dev/null +++ b/src/model/manifest.rs @@ -0,0 +1,976 @@ +use crate::model::common::der_take_tlv; +use crate::model::common::{BigUnsigned, UtcTime}; +use crate::model::oid::{OID_CT_RPKI_MANIFEST, OID_SHA256}; +use crate::model::rc::ResourceCertificate; +use crate::model::signed_object::{ + RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectDecodeError, SignedObjectParseError, + SignedObjectValidateError, +}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ManifestObject { + pub signed_object: RpkiSignedObject, + pub econtent_type: String, + pub manifest: ManifestEContent, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ManifestObjectParsed { + pub signed_object: RpkiSignedObjectParsed, + pub econtent_type: String, + pub manifest: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ManifestEContent { + pub version: u32, + pub manifest_number: BigUnsigned, + pub this_update: UtcTime, + pub next_update: UtcTime, + pub file_hash_alg: String, + /// DER-encoded content bytes of `Manifest.fileList` (SEQUENCE OF FileAndHash). + pub file_list_der: Vec, + /// Count of FileAndHash entries in `fileList`. + pub file_count: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ManifestEContentParsed { + der: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct FileAndHash { + pub file_name: String, + pub hash_bytes: [u8; 32], +} + +#[derive(Debug, thiserror::Error)] +pub enum ManifestParseError { + #[error("signed object parse error: {0} (RFC 6488 §2-§3; RFC 9589 §4)")] + SignedObject(#[from] SignedObjectParseError), + #[error("DER parse error: {0} (RFC 9286 §4.2; DER)")] + Parse(String), + + #[error("trailing bytes after DER object: {0} bytes (RFC 9286 §4.2; DER)")] + TrailingBytes(usize), +} + +#[derive(Debug, thiserror::Error)] +pub enum ManifestProfileError { + #[error("signed object profile error: {0} (RFC 6488 §2-§3; RFC 9589 §4)")] + SignedObject(#[from] SignedObjectValidateError), + + #[error( + "eContentType must be id-ct-rpkiManifest ({OID_CT_RPKI_MANIFEST}), got {0} (RFC 9286 §4.1; RFC 9286 §4.4(1))" + )] + InvalidEContentType(String), + + #[error("manifest profile decode error: {0} (RFC 9286 §4.2; DER)")] + ProfileDecode(String), + + #[error("Manifest must be a SEQUENCE of 5 or 6 elements, got {0} (RFC 9286 §4.2)")] + InvalidManifestSequenceLen(usize), + + #[error("Manifest.version must be 0, got {0} (RFC 9286 §4.2.1)")] + InvalidManifestVersion(u64), + + #[error( + "Manifest.manifestNumber must be non-negative INTEGER (RFC 9286 §4.2; RFC 9286 §4.2.1)" + )] + InvalidManifestNumber, + + #[error("Manifest.manifestNumber longer than 20 octets (RFC 9286 §4.2.1)")] + ManifestNumberTooLong, + + #[error("Manifest.thisUpdate must be GeneralizedTime (RFC 9286 §4.2)")] + InvalidThisUpdate, + + #[error("Manifest.nextUpdate must be GeneralizedTime (RFC 9286 §4.2)")] + InvalidNextUpdate, + + #[error("Manifest.nextUpdate must be later than thisUpdate (RFC 9286 §4.2.1)")] + NextUpdateNotLater, + + #[error( + "Manifest.fileHashAlg must be id-sha256 ({OID_SHA256}), got {0} (RFC 9286 §4.2.1; RFC 7935 §2)" + )] + InvalidFileHashAlg(String), + + #[error("Manifest.fileList must be a SEQUENCE (RFC 9286 §4.2)")] + InvalidFileList, + + #[error("FileAndHash must be SEQUENCE of 2 (RFC 9286 §4.2)")] + InvalidFileAndHash, + + #[error("fileList file name invalid: {0} (RFC 9286 §4.2.2)")] + InvalidFileName(String), + + #[error("fileList hash must be BIT STRING (RFC 9286 §4.2)")] + InvalidHashType, + + #[error( + "fileList hash BIT STRING must be octet-aligned (unused bits=0) (RFC 9286 §4.2.1; DER BIT STRING)" + )] + HashNotOctetAligned, + + #[error( + "fileList hash length invalid for sha256: got {0} bytes (RFC 9286 §4.2.1; RFC 7935 §2)" + )] + InvalidHashLength(usize), +} + +impl From for ManifestProfileError { + fn from(value: SignedObjectDecodeError) -> Self { + match value { + SignedObjectDecodeError::Parse(e) => ManifestProfileError::ProfileDecode(e.to_string()), + SignedObjectDecodeError::Validate(e) => ManifestProfileError::SignedObject(e), + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum ManifestDecodeError { + #[error("{0}")] + Parse(#[from] ManifestParseError), + + #[error("{0}")] + Validate(#[from] ManifestProfileError), +} + +#[derive(Debug, thiserror::Error)] +pub enum ManifestValidateError { + #[error( + "Manifest EE certificate MUST include at least one RFC 3779 resource extension (IP or AS) (RFC 6487 §4.8.10-§4.8.11; RFC 3779; RFC 9286 §5.1)" + )] + EeResourcesMissing, + + #[error( + "Manifest EE certificate IP resources MUST use inherit only (RFC 9286 §5.1; RFC 3779 §2.2.3.5)" + )] + EeIpResourcesNotInherit, + + #[error( + "Manifest EE certificate AS resources MUST use inherit only (RFC 9286 §5.1; RFC 3779 §3.2.3.3)" + )] + EeAsResourcesNotInherit, + + #[error( + "Manifest EE certificate AS resources rdi MUST be absent (RFC 6487 §4.8.11; RFC 3779 §3.2.3.5)" + )] + EeAsResourcesRdiPresent, +} + +impl ManifestObject { + /// Parse step of scheme A (`parse → validate → verify`). + pub fn parse_der(der: &[u8]) -> Result { + let signed_object = RpkiSignedObject::parse_der(der)?; + let econtent_type = signed_object + .signed_data + .encap_content_info + .econtent_type + .clone(); + let manifest = signed_object + .signed_data + .encap_content_info + .econtent + .as_deref() + .map(ManifestEContent::parse_der) + .transpose()?; + Ok(ManifestObjectParsed { + signed_object, + econtent_type, + manifest, + }) + } + + /// Profile validate step of scheme A (`parse → validate → verify`). + /// + /// `ManifestObject` is already profile-validated when constructed via `decode_der()` / + /// `ManifestObjectParsed::validate_profile()`. + pub fn validate_profile(&self) -> Result<(), ManifestProfileError> { + Ok(()) + } + + pub fn decode_der(der: &[u8]) -> Result { + Ok(Self::parse_der(der)?.validate_profile()?) + } + + pub fn decode_der_with_strict_options( + der: &[u8], + strict_cms_der: bool, + strict_name: bool, + ) -> Result { + let signed_object = + RpkiSignedObject::decode_der_with_strict_options(der, strict_cms_der, strict_name) + .map_err(ManifestProfileError::from)?; + Self::from_signed_object(signed_object) + } + + pub fn from_signed_object( + signed_object: RpkiSignedObject, + ) -> Result { + let econtent_type = signed_object + .signed_data + .encap_content_info + .econtent_type + .clone(); + if econtent_type != OID_CT_RPKI_MANIFEST { + return Err(ManifestProfileError::InvalidEContentType(econtent_type).into()); + } + let manifest = + ManifestEContent::decode_der(&signed_object.signed_data.encap_content_info.econtent)?; + Ok(Self { + signed_object, + econtent_type: OID_CT_RPKI_MANIFEST.to_string(), + manifest, + }) + } + + /// Validate the embedded EE certificate resources against RFC 9286 §5.1. + /// + /// This does **not** perform certificate path validation. It assumes `ee` is a parsed and + /// profile-validated RPKI EE resource certificate. + pub fn validate_against_ee_cert( + &self, + ee: &ResourceCertificate, + ) -> Result<(), ManifestValidateError> { + let ip = ee.tbs.extensions.ip_resources.as_ref(); + let asn = ee.tbs.extensions.as_resources.as_ref(); + if ip.is_none() && asn.is_none() { + return Err(ManifestValidateError::EeResourcesMissing); + } + + if let Some(ip) = ip + && !ip.is_all_inherit() + { + return Err(ManifestValidateError::EeIpResourcesNotInherit); + } + + if let Some(asn) = asn { + if asn.rdi.is_some() { + return Err(ManifestValidateError::EeAsResourcesRdiPresent); + } + if !asn.is_asnum_inherit() { + return Err(ManifestValidateError::EeAsResourcesNotInherit); + } + } + + Ok(()) + } + + /// Validate this manifest's embedded EE certificate resources. + pub fn validate_embedded_ee_cert(&self) -> Result<(), ManifestValidateError> { + let ee = &self.signed_object.signed_data.certificates[0].resource_cert; + self.validate_against_ee_cert(ee) + } +} + +impl ManifestEContent { + /// Parse step of scheme A (`parse → validate → verify`). + pub fn parse_der(der: &[u8]) -> Result { + let (_tag, _value, rem) = der_take_tlv(der).map_err(ManifestParseError::Parse)?; + if !rem.is_empty() { + return Err(ManifestParseError::TrailingBytes(rem.len())); + } + Ok(ManifestEContentParsed { der: der.to_vec() }) + } + + /// Profile validate step of scheme A (`parse → validate → verify`). + /// + /// `ManifestEContent` is already profile-validated when constructed via `decode_der()` / + /// `ManifestEContentParsed::validate_profile()`. + pub fn validate_profile(&self) -> Result<(), ManifestProfileError> { + Ok(()) + } + + /// Decode the DER-encoded Manifest eContent defined in RFC 9286 §4.2 (`parse + validate`). + pub fn decode_der(der: &[u8]) -> Result { + Ok(Self::parse_der(der)?.validate_profile()?) + } + + /// Parse and return the manifest fileList. + /// + /// Note: `ManifestEContent` is profile-validated when produced via `decode_der()`, so this + /// should only fail due to internal inconsistencies (or if constructed manually). + pub fn parse_files(&self) -> Result, ManifestProfileError> { + parse_file_list_sha256_fast(&self.file_list_der) + } + + pub fn file_count(&self) -> usize { + self.file_count + } +} + +impl ManifestObjectParsed { + pub fn validate_profile(self) -> Result { + let signed_object = self.signed_object.validate_profile()?; + let econtent_type = signed_object + .signed_data + .encap_content_info + .econtent_type + .clone(); + if econtent_type != OID_CT_RPKI_MANIFEST { + return Err(ManifestProfileError::InvalidEContentType(econtent_type)); + } + let manifest = self + .manifest + .ok_or_else(|| ManifestProfileError::ProfileDecode("Manifest.eContent missing".into()))? + .validate_profile()?; + Ok(ManifestObject { + signed_object, + econtent_type: OID_CT_RPKI_MANIFEST.to_string(), + manifest, + }) + } +} + +impl ManifestEContentParsed { + pub fn validate_profile(self) -> Result { + decode_manifest_econtent_fast(&self.der) + } +} + +fn validate_file_name_bytes(bytes: &[u8]) -> Result<(), ManifestProfileError> { + // RFC 9286 §4.2.2: + // 1+ chars from a-zA-Z0-9-_ , then '.', then 3-letter extension. + if bytes.len() < 5 { + return Err(ManifestProfileError::InvalidFileName( + String::from_utf8_lossy(bytes).into_owned(), + )); + }; + + // "followed by a single . (DOT), followed by a three letter extension" + // -> the dot must be exactly 4 bytes from the end. + let dot_pos = bytes.len() - 4; + if bytes[dot_pos] != b'.' { + return Err(ManifestProfileError::InvalidFileName( + String::from_utf8_lossy(bytes).into_owned(), + )); + } + + #[inline(always)] + fn valid_base_char(b: u8) -> bool { + // RFC 9286 allowed set: a-zA-Z0-9-_ + b.is_ascii_digit() + || b.is_ascii_lowercase() + || b.is_ascii_uppercase() + || b == b'-' + || b == b'_' + } + + for &b in &bytes[..dot_pos] { + if (b & 0x80) != 0 || !valid_base_char(b) { + return Err(ManifestProfileError::InvalidFileName( + String::from_utf8_lossy(bytes).into_owned(), + )); + } + } + + let e0 = bytes[dot_pos + 1]; + let e1 = bytes[dot_pos + 2]; + let e2 = bytes[dot_pos + 3]; + if (e0 & 0x80) != 0 || (e1 & 0x80) != 0 || (e2 & 0x80) != 0 { + return Err(ManifestProfileError::InvalidFileName( + String::from_utf8_lossy(bytes).into_owned(), + )); + } + + #[inline(always)] + fn lower_if_alpha(b: u8) -> Option { + match b { + b'a'..=b'z' => Some(b), + b'A'..=b'Z' => Some(b + 32), + _ => None, + } + } + + let Some(l0) = lower_if_alpha(e0) else { + return Err(ManifestProfileError::InvalidFileName( + String::from_utf8_lossy(bytes).into_owned(), + )); + }; + let Some(l1) = lower_if_alpha(e1) else { + return Err(ManifestProfileError::InvalidFileName( + String::from_utf8_lossy(bytes).into_owned(), + )); + }; + let Some(l2) = lower_if_alpha(e2) else { + return Err(ManifestProfileError::InvalidFileName( + String::from_utf8_lossy(bytes).into_owned(), + )); + }; + + match [l0, l1, l2] { + // Full IANA list (see `common.rs`). + [b'a', b's', b'a'] + | [b'c', b'e', b'r'] + | [b'c', b'r', b'l'] + | [b'g', b'b', b'r'] + | [b'm', b'f', b't'] + | [b'r', b'o', b'a'] + | [b's', b'i', b'g'] + | [b't', b'a', b'k'] => Ok(()), + _ => Err(ManifestProfileError::InvalidFileName( + String::from_utf8_lossy(bytes).into_owned(), + )), + } +} + +fn decode_manifest_econtent_fast(der: &[u8]) -> Result { + let (tag, mut seq_content, rem) = der_take_tlv(der) + .map_err(|e| ManifestProfileError::ProfileDecode(format!("DER decode error: {e}")))?; + if !rem.is_empty() { + return Err(ManifestProfileError::ProfileDecode(format!( + "trailing bytes after DER object: {} bytes", + rem.len() + ))); + } + if tag != 0x30 { + return Err(ManifestProfileError::ProfileDecode( + "Manifest eContent must be SEQUENCE".into(), + )); + } + + let seq_len = der_count_elements(seq_content).map_err(ManifestProfileError::ProfileDecode)?; + if seq_len != 5 && seq_len != 6 { + return Err(ManifestProfileError::InvalidManifestSequenceLen(seq_len)); + } + + let mut version: u32 = 0; + if seq_len == 6 { + let Some(&first_tag) = seq_content.first() else { + return Err(ManifestProfileError::InvalidManifestSequenceLen(0)); + }; + if first_tag != 0xA0 { + return Err(ManifestProfileError::ProfileDecode( + "Manifest.version must be [0] EXPLICIT INTEGER".into(), + )); + } + + let (_cs_tag, cs_value, after) = der_take_tlv(seq_content).map_err(|e| { + ManifestProfileError::ProfileDecode(format!("Manifest.version decode error: {e}")) + })?; + seq_content = after; + + let (inner_tag, inner_value, inner_rem) = der_take_tlv(cs_value).map_err(|e| { + ManifestProfileError::ProfileDecode(format!("Manifest.version inner decode error: {e}")) + })?; + if !inner_rem.is_empty() { + return Err(ManifestProfileError::ProfileDecode( + "trailing bytes inside Manifest.version".into(), + )); + } + if inner_tag != 0x02 { + return Err(ManifestProfileError::ProfileDecode( + "Manifest.version must be [0] EXPLICIT INTEGER".into(), + )); + } + let v = der_integer_to_u64(inner_value).map_err(|e| { + ManifestProfileError::ProfileDecode(format!("Manifest.version decode error: {e}")) + })?; + if v != 0 { + return Err(ManifestProfileError::InvalidManifestVersion(v)); + } + version = 0; + } + + let (mn_tag, mn_value, after) = der_take_tlv(seq_content).map_err(|e| { + ManifestProfileError::ProfileDecode(format!("Manifest.manifestNumber decode error: {e}")) + })?; + seq_content = after; + if mn_tag != 0x02 { + return Err(ManifestProfileError::InvalidManifestNumber); + } + let manifest_number = der_integer_to_bigunsigned(mn_value)?; + + let (tu_tag, tu_value, after) = der_take_tlv(seq_content).map_err(|e| { + ManifestProfileError::ProfileDecode(format!("Manifest.thisUpdate decode error: {e}")) + })?; + seq_content = after; + if tu_tag != 0x18 { + return Err(ManifestProfileError::InvalidThisUpdate); + } + let this_update = + parse_generalized_time_bytes(tu_value).map_err(ManifestProfileError::ProfileDecode)?; + + let (nu_tag, nu_value, after) = der_take_tlv(seq_content).map_err(|e| { + ManifestProfileError::ProfileDecode(format!("Manifest.nextUpdate decode error: {e}")) + })?; + seq_content = after; + if nu_tag != 0x18 { + return Err(ManifestProfileError::InvalidNextUpdate); + } + let next_update = + parse_generalized_time_bytes(nu_value).map_err(ManifestProfileError::ProfileDecode)?; + if next_update <= this_update { + return Err(ManifestProfileError::NextUpdateNotLater); + } + + let (oid_tag, oid_value, after) = der_take_tlv(seq_content).map_err(|e| { + ManifestProfileError::ProfileDecode(format!("Manifest.fileHashAlg decode error: {e}")) + })?; + seq_content = after; + if oid_tag != 0x06 { + return Err(ManifestProfileError::ProfileDecode( + "Manifest.fileHashAlg must be OBJECT IDENTIFIER".into(), + )); + } + if !oid_content_is_sha256(oid_value) { + return Err(ManifestProfileError::InvalidFileHashAlg( + oid_content_to_string(oid_value), + )); + } + + let (fl_tag, fl_value, after) = der_take_tlv(seq_content).map_err(|e| { + ManifestProfileError::ProfileDecode(format!("Manifest.fileList decode error: {e}")) + })?; + seq_content = after; + if fl_tag != 0x30 { + return Err(ManifestProfileError::InvalidFileList); + } + let file_count = validate_file_list_sha256_fast(fl_value)?; + let file_list_der = fl_value.to_vec(); + + if !seq_content.is_empty() { + return Err(ManifestProfileError::InvalidManifestSequenceLen(seq_len)); + } + + Ok(ManifestEContent { + version, + manifest_number, + this_update, + next_update, + file_hash_alg: OID_SHA256.to_string(), + file_list_der, + file_count, + }) +} + +fn validate_file_list_sha256_fast(content: &[u8]) -> Result { + let mut cur = content; + let mut count: usize = 0; + while !cur.is_empty() { + let (tag, value, rem) = der_take_tlv(cur).map_err(|e| { + ManifestProfileError::ProfileDecode(format!("fileList entry decode error: {e}")) + })?; + cur = rem; + if tag != 0x30 { + return Err(ManifestProfileError::InvalidFileAndHash); + } + + let mut entry = value; + let (fn_tag, fn_value, entry_rem) = der_take_tlv(entry).map_err(|e| { + ManifestProfileError::ProfileDecode(format!("fileList fileName decode error: {e}")) + })?; + entry = entry_rem; + if fn_tag != 0x16 { + return Err(ManifestProfileError::InvalidFileAndHash); + } + if entry.is_empty() { + return Err(ManifestProfileError::InvalidFileAndHash); + } + validate_file_name_bytes(fn_value)?; + + let (hash_tag, hash_value, entry_rem) = der_take_tlv(entry).map_err(|_e| { + // Missing second element should map to "SEQUENCE of 2" shape error. + ManifestProfileError::InvalidFileAndHash + })?; + entry = entry_rem; + if !entry.is_empty() { + return Err(ManifestProfileError::InvalidFileAndHash); + } + if hash_tag != 0x03 { + return Err(ManifestProfileError::InvalidHashType); + } + if hash_value.is_empty() { + return Err(ManifestProfileError::InvalidHashLength(0)); + } + let unused_bits = hash_value[0]; + if unused_bits != 0 { + return Err(ManifestProfileError::HashNotOctetAligned); + } + let bits = &hash_value[1..]; + if bits.len() != 32 { + return Err(ManifestProfileError::InvalidHashLength(bits.len())); + } + count += 1; + } + Ok(count) +} + +fn parse_file_list_sha256_fast(content: &[u8]) -> Result, ManifestProfileError> { + // Heuristic initial capacity (avoid a full pre-scan, which is expensive for xlarge manifests). + // Each FileAndHash entry is typically tens of bytes; 80 is a conservative average. + let est = (content.len() / 80).clamp(16, 4096); + let mut cur = content; + let mut out: Vec = Vec::with_capacity(est); + while !cur.is_empty() { + let (tag, value, rem) = der_take_tlv(cur).map_err(|e| { + ManifestProfileError::ProfileDecode(format!("fileList entry decode error: {e}")) + })?; + cur = rem; + if tag != 0x30 { + return Err(ManifestProfileError::InvalidFileAndHash); + } + let mut entry = value; + let (fn_tag, fn_value, entry_rem) = der_take_tlv(entry).map_err(|e| { + ManifestProfileError::ProfileDecode(format!("fileList fileName decode error: {e}")) + })?; + entry = entry_rem; + if fn_tag != 0x16 { + return Err(ManifestProfileError::InvalidFileAndHash); + } + if entry.is_empty() { + return Err(ManifestProfileError::InvalidFileAndHash); + } + let file_name = validate_and_copy_file_name(fn_value)?; + + let (hash_tag, hash_value, entry_rem) = der_take_tlv(entry).map_err(|_e| { + // Missing second element should map to "SEQUENCE of 2" shape error. + ManifestProfileError::InvalidFileAndHash + })?; + entry = entry_rem; + if !entry.is_empty() { + return Err(ManifestProfileError::InvalidFileAndHash); + } + if hash_tag != 0x03 { + return Err(ManifestProfileError::InvalidHashType); + } + if hash_value.is_empty() { + return Err(ManifestProfileError::InvalidHashLength(0)); + } + let unused_bits = hash_value[0]; + if unused_bits != 0 { + return Err(ManifestProfileError::HashNotOctetAligned); + } + let bits = &hash_value[1..]; + if bits.len() != 32 { + return Err(ManifestProfileError::InvalidHashLength(bits.len())); + } + let mut hash_bytes = [0u8; 32]; + hash_bytes.copy_from_slice(bits); + out.push(FileAndHash { + file_name, + hash_bytes, + }); + } + Ok(out) +} + +fn validate_and_copy_file_name(bytes: &[u8]) -> Result { + validate_file_name_bytes(bytes)?; + String::from_utf8(bytes.to_vec()).map_err(|_| { + ManifestProfileError::InvalidFileName(String::from_utf8_lossy(bytes).into_owned()) + }) +} + +fn der_count_elements(mut input: &[u8]) -> Result { + let mut count: usize = 0; + while !input.is_empty() { + let (_tag, _value, rem) = der_take_tlv(input)?; + input = rem; + count += 1; + } + Ok(count) +} + +fn der_integer_to_u64(bytes: &[u8]) -> Result { + if bytes.is_empty() { + return Err("INTEGER empty".into()); + } + // Reject negative (two's complement). + if bytes[0] & 0x80 != 0 { + return Err("INTEGER is negative".into()); + } + if bytes.len() > 8 { + return Err("INTEGER too large".into()); + } + let mut v: u64 = 0; + for &b in bytes { + v = (v << 8) | (b as u64); + } + Ok(v) +} + +fn der_integer_to_bigunsigned(bytes: &[u8]) -> Result { + if bytes.is_empty() { + return Err(ManifestProfileError::InvalidManifestNumber); + } + // Two's complement: for non-negative values, a leading 0x00 may be present. + if bytes[0] & 0x80 != 0 { + return Err(ManifestProfileError::InvalidManifestNumber); + } + let mut start = 0usize; + while start + 1 < bytes.len() && bytes[start] == 0 { + start += 1; + } + let mut minimal = bytes[start..].to_vec(); + if minimal.is_empty() { + minimal.push(0); + } + if minimal.len() > 20 { + return Err(ManifestProfileError::ManifestNumberTooLong); + } + Ok(BigUnsigned { bytes_be: minimal }) +} + +fn parse_generalized_time_bytes(bytes: &[u8]) -> Result { + // Accept "YYYYMMDDHHMMSSZ" and also allow optional fractional seconds (".fff...Z"). + if !bytes.is_ascii() { + return Err("GeneralizedTime not ASCII".into()); + } + let s = std::str::from_utf8(bytes).map_err(|e| e.to_string())?; + if !s.ends_with('Z') { + return Err("GeneralizedTime must end with 'Z'".into()); + } + let core = &s[..s.len() - 1]; + let (main, frac) = core + .split_once('.') + .map_or((core, None), |(a, b)| (a, Some(b))); + if main.len() != 14 || !main.bytes().all(|b| b.is_ascii_digit()) { + return Err("GeneralizedTime must be YYYYMMDDHHMMSS[.fff]Z".into()); + } + let year: i32 = main[0..4].parse().map_err(|_| "bad year")?; + let month: u8 = main[4..6].parse().map_err(|_| "bad month")?; + let day: u8 = main[6..8].parse().map_err(|_| "bad day")?; + let hour: u8 = main[8..10].parse().map_err(|_| "bad hour")?; + let minute: u8 = main[10..12].parse().map_err(|_| "bad minute")?; + let second: u8 = main[12..14].parse().map_err(|_| "bad second")?; + + let nanosecond: u32 = if let Some(frac) = frac { + if frac.is_empty() || !frac.bytes().all(|b| b.is_ascii_digit()) { + return Err("bad fractional seconds".into()); + } + let mut ns: u32 = 0; + let mut scale: u32 = 1_000_000_000; + for (i, ch) in frac.bytes().enumerate() { + if i >= 9 { + break; + } + scale /= 10; + ns += ((ch - b'0') as u32) * scale; + } + ns + } else { + 0 + }; + + let date = time::Date::from_calendar_date( + year, + time::Month::try_from(month).map_err(|_| "bad month")?, + day, + ) + .map_err(|e| e.to_string())?; + let t = + time::Time::from_hms_nano(hour, minute, second, nanosecond).map_err(|e| e.to_string())?; + Ok(date.with_time(t).assume_utc()) +} + +fn oid_content_is_sha256(bytes: &[u8]) -> bool { + // 2.16.840.1.101.3.4.2.1 + let mut arcs = oid_content_iter(bytes); + const EXPECTED: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 2, 1]; + for &e in EXPECTED { + match arcs.next() { + Some(v) if v == e => {} + _ => return false, + } + } + arcs.next().is_none() +} + +fn oid_content_to_string(bytes: &[u8]) -> String { + let arcs: Vec = oid_content_iter(bytes).collect(); + if arcs.is_empty() { + return "".to_string(); + } + let mut s = String::new(); + for (i, a) in arcs.iter().enumerate() { + if i > 0 { + s.push('.'); + } + s.push_str(&a.to_string()); + } + s +} + +fn oid_content_iter(bytes: &[u8]) -> impl Iterator + '_ { + struct It<'a> { + bytes: &'a [u8], + pos: usize, + first_done: bool, + first_a0: u64, + first_a1: u64, + emit_first_idx: u8, + } + impl<'a> Iterator for It<'a> { + type Item = u64; + fn next(&mut self) -> Option { + if !self.first_done { + if self.bytes.is_empty() { + self.first_done = true; + return None; + } + let first = self.bytes[0] as u64; + self.first_a0 = first / 40; + self.first_a1 = first % 40; + self.pos = 1; + self.first_done = true; + self.emit_first_idx = 0; + } + if self.emit_first_idx == 0 { + self.emit_first_idx = 1; + return Some(self.first_a0); + } + if self.emit_first_idx == 1 { + self.emit_first_idx = 2; + return Some(self.first_a1); + } + if self.pos >= self.bytes.len() { + return None; + } + let mut v: u64 = 0; + while self.pos < self.bytes.len() { + let b = self.bytes[self.pos]; + self.pos += 1; + v = (v << 7) | ((b & 0x7F) as u64); + if b & 0x80 == 0 { + return Some(v); + } + } + None + } + } + It { + bytes, + pos: 0, + first_done: false, + first_a0: 0, + first_a1: 0, + emit_first_idx: 0, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn tlv(tag: u8, value: &[u8]) -> Vec { + assert!(value.len() < 128); + let mut out = Vec::with_capacity(2 + value.len()); + out.push(tag); + out.push(value.len() as u8); + out.extend_from_slice(value); + out + } + + fn tlv_long_len(tag: u8, len_bytes: &[u8], value: &[u8]) -> Vec { + let mut out = Vec::with_capacity(2 + len_bytes.len() + value.len()); + out.push(tag); + out.push(0x80 | (len_bytes.len() as u8)); + out.extend_from_slice(len_bytes); + out.extend_from_slice(value); + out + } + + #[test] + fn der_take_tlv_supports_short_and_long_form_lengths_and_errors() { + let v = b"abc"; + let der = tlv(0x04, v); + let (tag, val, rem) = der_take_tlv(&der).expect("short len"); + assert_eq!(tag, 0x04); + assert_eq!(val, v); + assert!(rem.is_empty()); + + // Long-form length with 1 length byte (130). + let v = vec![b'x'; 130]; + let der = tlv_long_len(0x04, &[0x82], &v); + let (tag, val, rem) = der_take_tlv(&der).expect("long len 1"); + assert_eq!(tag, 0x04); + assert_eq!(val.len(), 130); + assert!(rem.is_empty()); + + // Long-form length with 2 length bytes (256). + let v = vec![b'y'; 256]; + let der = tlv_long_len(0x04, &[0x01, 0x00], &v); + let (tag, val, rem) = der_take_tlv(&der).expect("long len 2"); + assert_eq!(tag, 0x04); + assert_eq!(val.len(), 256); + assert!(rem.is_empty()); + + assert!(der_take_tlv(&[]).is_err()); + assert!(der_take_tlv(&[0x04]).is_err()); + + // High-tag-number form not supported. + assert!(der_take_tlv(&[0x1F, 0x01, 0x00]).is_err()); + + // Indefinite length is not allowed in DER. + assert!(der_take_tlv(&[0x04, 0x80]).is_err()); + + // Invalid long-form length encoding. + assert!(der_take_tlv(&[0x04, 0x81]).is_err()); + assert!(der_take_tlv(&[0x04, 0x89]).is_err()); + } + + #[test] + fn parse_generalized_time_bytes_accepts_fraction_and_rejects_invalid() { + let t = parse_generalized_time_bytes(b"20260101000000Z").expect("basic time"); + assert_eq!(t.year(), 2026); + + let t = parse_generalized_time_bytes(b"20260101000000.1Z").expect("fractional"); + assert_eq!(t.nanosecond(), 100_000_000); + + assert!(parse_generalized_time_bytes(b"20260101000000").is_err()); + assert!(parse_generalized_time_bytes(b"20260101000000+00").is_err()); + assert!(parse_generalized_time_bytes(b"2026010100000Z").is_err()); + assert!(parse_generalized_time_bytes(b"20261301000000Z").is_err()); + assert!(parse_generalized_time_bytes(b"20260132000000Z").is_err()); + assert!(parse_generalized_time_bytes(&[0xFF]).is_err()); + } + + #[test] + fn oid_helpers_accept_sha256_and_format_invalid() { + // 2.16.840.1.101.3.4.2.1 + let sha256_oid_content = [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01]; + assert!(oid_content_is_sha256(&sha256_oid_content)); + assert!(!oid_content_is_sha256(&[0x55, 0x04, 0x03])); // 2.5.4.3 + + assert_eq!(oid_content_to_string(&[]), "".to_string()); + } + + #[test] + fn validate_file_list_sha256_fast_counts_and_rejects_bad_hash() { + fn file_and_hash(file: &str, digest: u8) -> Vec { + let mut hash = vec![0u8; 33]; + hash[0] = 0; // unused bits + for b in &mut hash[1..] { + *b = digest; + } + let ia5 = tlv(0x16, file.as_bytes()); + let bit = tlv(0x03, &hash); + let mut entry = Vec::new(); + entry.extend_from_slice(&ia5); + entry.extend_from_slice(&bit); + tlv(0x30, &entry) + } + + let mut list = Vec::new(); + list.extend_from_slice(&file_and_hash("A.cer", 0xAA)); + list.extend_from_slice(&file_and_hash("B.roa", 0xBB)); + assert_eq!(validate_file_list_sha256_fast(&list).expect("count"), 2); + + // Wrong hash length. + let mut bad = Vec::new(); + let ia5 = tlv(0x16, b"A.cer"); + let bit = tlv(0x03, &[0u8; 2]); // too short + let mut entry = Vec::new(); + entry.extend_from_slice(&ia5); + entry.extend_from_slice(&bit); + bad.extend_from_slice(&tlv(0x30, &entry)); + assert!(matches!( + validate_file_list_sha256_fast(&bad), + Err(ManifestProfileError::InvalidHashLength(_)) + )); + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs new file mode 100644 index 0000000..f95d7b4 --- /dev/null +++ b/src/model/mod.rs @@ -0,0 +1,13 @@ +pub mod aspa; +pub mod common; +pub mod crl; +pub mod manifest; +pub mod oid; +pub mod rc; +pub mod roa; +pub mod signed_object; +pub mod ta; +pub mod tal; + +pub mod projection; +pub mod router_cert; diff --git a/src/model/oid.rs b/src/model/oid.rs new file mode 100644 index 0000000..a5504cb --- /dev/null +++ b/src/model/oid.rs @@ -0,0 +1,78 @@ +pub const OID_SHA256: &str = "2.16.840.1.101.3.4.2.1"; +pub const OID_SHA256_RAW: &[u8] = &asn1_rs::oid!(raw 2.16.840.1.101.3.4.2.1); + +pub const OID_SIGNED_DATA: &str = "1.2.840.113549.1.7.2"; +pub const OID_SIGNED_DATA_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.7.2); + +pub const OID_CMS_ATTR_CONTENT_TYPE: &str = "1.2.840.113549.1.9.3"; +pub const OID_CMS_ATTR_CONTENT_TYPE_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.9.3); +pub const OID_CMS_ATTR_MESSAGE_DIGEST: &str = "1.2.840.113549.1.9.4"; +pub const OID_CMS_ATTR_MESSAGE_DIGEST_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.9.4); +pub const OID_CMS_ATTR_SIGNING_TIME: &str = "1.2.840.113549.1.9.5"; +pub const OID_CMS_ATTR_SIGNING_TIME_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.9.5); + +pub const OID_RSA_ENCRYPTION: &str = "1.2.840.113549.1.1.1"; +pub const OID_RSA_ENCRYPTION_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.1.1); +pub const OID_SHA256_WITH_RSA_ENCRYPTION: &str = "1.2.840.113549.1.1.11"; +pub const OID_SHA256_WITH_RSA_ENCRYPTION_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.1.11); + +// X.509 extensions (RFC 5280 / RFC 6487) +pub const OID_BASIC_CONSTRAINTS: &str = "2.5.29.19"; +pub const OID_BASIC_CONSTRAINTS_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.19); +pub const OID_KEY_USAGE: &str = "2.5.29.15"; +pub const OID_KEY_USAGE_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.15); +pub const OID_EXTENDED_KEY_USAGE: &str = "2.5.29.37"; +pub const OID_EXTENDED_KEY_USAGE_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.37); +pub const OID_CRL_DISTRIBUTION_POINTS: &str = "2.5.29.31"; +pub const OID_CRL_DISTRIBUTION_POINTS_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.31); +pub const OID_AUTHORITY_INFO_ACCESS: &str = "1.3.6.1.5.5.7.1.1"; +pub const OID_AUTHORITY_INFO_ACCESS_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.1.1); +pub const OID_CERTIFICATE_POLICIES: &str = "2.5.29.32"; +pub const OID_CERTIFICATE_POLICIES_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.32); +pub const OID_QT_CPS: &str = "1.3.6.1.5.5.7.2.1"; + +pub const OID_AUTHORITY_KEY_IDENTIFIER: &str = "2.5.29.35"; +pub const OID_AUTHORITY_KEY_IDENTIFIER_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.35); +pub const OID_CRL_NUMBER: &str = "2.5.29.20"; +pub const OID_CRL_NUMBER_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.20); +pub const OID_SUBJECT_KEY_IDENTIFIER: &str = "2.5.29.14"; +pub const OID_SUBJECT_KEY_IDENTIFIER_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.14); + +pub const OID_CT_RPKI_MANIFEST: &str = "1.2.840.113549.1.9.16.1.26"; +pub const OID_CT_RPKI_MANIFEST_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.9.16.1.26); +pub const OID_CT_ROUTE_ORIGIN_AUTHZ: &str = "1.2.840.113549.1.9.16.1.24"; +pub const OID_CT_ROUTE_ORIGIN_AUTHZ_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.9.16.1.24); +pub const OID_CT_ASPA: &str = "1.2.840.113549.1.9.16.1.49"; +pub const OID_CT_ASPA_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.9.16.1.49); + +// X.509 extensions / access methods (RFC 5280 / RFC 6487) +pub const OID_SUBJECT_INFO_ACCESS: &str = "1.3.6.1.5.5.7.1.11"; +pub const OID_SUBJECT_INFO_ACCESS_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.1.11); +pub const OID_AD_SIGNED_OBJECT: &str = "1.3.6.1.5.5.7.48.11"; +pub const OID_AD_SIGNED_OBJECT_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.48.11); + +pub const OID_AD_CA_ISSUERS: &str = "1.3.6.1.5.5.7.48.2"; +pub const OID_AD_CA_ISSUERS_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.48.2); +pub const OID_AD_CA_REPOSITORY: &str = "1.3.6.1.5.5.7.48.5"; +pub const OID_AD_CA_REPOSITORY_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.48.5); +pub const OID_AD_RPKI_MANIFEST: &str = "1.3.6.1.5.5.7.48.10"; +pub const OID_AD_RPKI_MANIFEST_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.48.10); +pub const OID_AD_RPKI_NOTIFY: &str = "1.3.6.1.5.5.7.48.13"; +pub const OID_AD_RPKI_NOTIFY_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.48.13); + +// RFC 3779 resource extensions (RFC 6487 profile) +pub const OID_IP_ADDR_BLOCKS: &str = "1.3.6.1.5.5.7.1.7"; +pub const OID_IP_ADDR_BLOCKS_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.1.7); +pub const OID_AUTONOMOUS_SYS_IDS: &str = "1.3.6.1.5.5.7.1.8"; +pub const OID_AUTONOMOUS_SYS_IDS_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.1.8); + +// RPKI CP (RFC 6484 / RFC 6487) +pub const OID_CP_IPADDR_ASNUMBER: &str = "1.3.6.1.5.5.7.14.2"; +pub const OID_CP_IPADDR_ASNUMBER_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.14.2); + +pub const OID_CT_RPKI_CCR: &str = "1.2.840.113549.1.9.16.1.54"; +pub const OID_CT_RPKI_CCR_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.9.16.1.54); + +pub const OID_KP_BGPSEC_ROUTER: &str = "1.3.6.1.5.5.7.3.30"; +pub const OID_EC_PUBLIC_KEY: &str = "1.2.840.10045.2.1"; +pub const OID_SECP256R1: &str = "1.2.840.10045.3.1.7"; diff --git a/src/model/projection.rs b/src/model/projection.rs new file mode 100644 index 0000000..25b0b25 --- /dev/null +++ b/src/model/projection.rs @@ -0,0 +1,663 @@ +use std::net::{Ipv4Addr, Ipv6Addr}; +use std::path::Path; + +use serde::{Deserialize, Serialize}; +use serde_json::{Value, json}; +use sha2::{Digest, Sha256}; + +use crate::model::aspa::AspaObject; +use crate::model::crl::RpkixCrl; +use crate::model::manifest::ManifestObject; +use crate::model::rc::{AccessDescription, RcExtensions, ResourceCertificate, SubjectInfoAccess}; +use crate::model::roa::{IpPrefix as RoaIpPrefix, RoaAfi, RoaObject}; +use crate::model::signed_object::{ + ResourceEeCertificate, RpkiSignedObject, SignedAttrsProfiled, SignerInfoProfiled, +}; +use crate::model::ta::TaCertificate; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ObjectType { + Auto, + Cer, + Mft, + Crl, + Roa, + Aspa, +} + +impl ObjectType { + pub fn parse(value: &str) -> Result { + match value.to_ascii_lowercase().as_str() { + "auto" => Ok(Self::Auto), + "cer" | ".cer" | "cert" | "certificate" => Ok(Self::Cer), + "mft" | ".mft" | "manifest" => Ok(Self::Mft), + "crl" | ".crl" => Ok(Self::Crl), + "roa" | ".roa" => Ok(Self::Roa), + "asa" | ".asa" | "aspa" => Ok(Self::Aspa), + _ => Err(format!("unsupported object type: {value}")), + } + } + + pub fn label(self) -> &'static str { + object_type_label(self) + } +} + +pub fn resolve_object_type(object_type: ObjectType, path: &Path) -> Result { + if object_type != ObjectType::Auto { + return Ok(object_type); + } + match path + .extension() + .and_then(|v| v.to_str()) + .map(|v| v.to_ascii_lowercase()) + .as_deref() + { + Some("cer") => Ok(ObjectType::Cer), + Some("mft") => Ok(ObjectType::Mft), + Some("crl") => Ok(ObjectType::Crl), + Some("roa") => Ok(ObjectType::Roa), + Some("asa") | Some("aspa") => Ok(ObjectType::Aspa), + _ => Err(format!( + "cannot infer object type from path: {}", + path.display() + )), + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ObjectProjectionRecord { + pub schema_version: u32, + pub sha256: String, + pub object_type: String, + pub parse_status: String, + pub error_summary: Option, + pub projection: Value, +} + +pub fn build_object_projection( + object_type: ObjectType, + input_path: &Path, + bytes: &[u8], + entry_limit: usize, +) -> ObjectProjectionRecord { + let resolved = match resolve_object_type(object_type, input_path) { + Ok(value) => value, + Err(err) => { + return ObjectProjectionRecord { + schema_version: 1, + sha256: sha256_hex(bytes), + object_type: "unknown".to_string(), + parse_status: "error".to_string(), + error_summary: Some(err), + projection: json!({"decode": {"profileValid": false}}), + }; + } + }; + let projection = parse_object_json(resolved, input_path, bytes, entry_limit); + let parse_status = if projection + .get("object") + .and_then(|v| v.get("decode")) + .and_then(|v| v.get("profileValid")) + .and_then(Value::as_bool) + .unwrap_or(false) + { + "ok" + } else { + "error" + }; + let error_summary = projection + .get("object") + .and_then(|v| v.get("decode")) + .and_then(|v| v.get("error")) + .and_then(Value::as_str) + .map(str::to_string); + ObjectProjectionRecord { + schema_version: 1, + sha256: sha256_hex(bytes), + object_type: resolved.label().to_string(), + parse_status: parse_status.to_string(), + error_summary, + projection, + } +} + +pub fn parse_object_json( + object_type: ObjectType, + input_path: &Path, + bytes: &[u8], + entry_limit: usize, +) -> Value { + let object = match object_type { + ObjectType::Auto => unreachable!("auto must be resolved"), + ObjectType::Cer => parse_cer_json(bytes), + ObjectType::Mft => parse_mft_json(bytes, entry_limit), + ObjectType::Crl => parse_crl_json(bytes, entry_limit), + ObjectType::Roa => parse_roa_json(bytes, entry_limit), + ObjectType::Aspa => parse_aspa_json(bytes, entry_limit), + }; + json!({ + "tool": "rpki_object_parse", + "schemaVersion": 1, + "input": { + "path": input_path.display().to_string(), + "type": object_type_label(object_type), + "bytes": bytes_summary(bytes), + }, + "object": object, + }) +} + +pub fn parse_cer_json(bytes: &[u8]) -> Value { + match ResourceCertificate::decode_der(bytes) { + Ok(cert) => { + let ta_profile = match TaCertificate::decode_der(bytes) { + Ok(ta) => json!({ + "valid": true, + "selfSignature": result_json(ta.verify_self_signature().map_err(|e| e.to_string())), + }), + Err(err) => json!({ + "valid": false, + "error": err.to_string(), + }), + }; + json!({ + "type": "cer", + "decode": {"profileValid": true}, + "resourceCertificate": resource_certificate_json(&cert), + "trustAnchorProfile": ta_profile, + }) + } + Err(err) => json!({ + "type": "cer", + "decode": {"profileValid": false, "error": err.to_string()}, + }), + } +} + +pub fn parse_mft_json(bytes: &[u8], entry_limit: usize) -> Value { + match ManifestObject::decode_der(bytes) { + Ok(mft) => { + let files = mft.manifest.parse_files(); + let (file_sample, file_list_error) = match files { + Ok(entries) => ( + json!({ + "count": entries.len(), + "truncated": entries.len() > entry_limit, + "entries": entries.iter().take(entry_limit).map(|item| { + json!({"fileName": item.file_name, "hashHex": hex::encode(item.hash_bytes)}) + }).collect::>(), + }), + Value::Null, + ), + Err(err) => (Value::Null, json!(err.to_string())), + }; + json!({ + "type": "mft", + "decode": {"profileValid": true}, + "eContentType": mft.econtent_type, + "signedObject": signed_object_json(&mft.signed_object), + "manifest": { + "version": mft.manifest.version, + "manifestNumberHex": mft.manifest.manifest_number.to_hex_upper(), + "thisUpdate": format_time(mft.manifest.this_update), + "nextUpdate": format_time(mft.manifest.next_update), + "fileHashAlg": mft.manifest.file_hash_alg, + "fileCount": mft.manifest.file_count(), + "fileList": file_sample, + "fileListError": file_list_error, + }, + "embeddedEeProfile": result_json(mft.validate_embedded_ee_cert().map_err(|e| e.to_string())), + "cmsSignature": result_json(mft.signed_object.verify_signature().map_err(|e| e.to_string())), + }) + } + Err(err) => json!({ + "type": "mft", + "decode": {"profileValid": false, "error": err.to_string()}, + }), + } +} + +pub fn parse_crl_json(bytes: &[u8], entry_limit: usize) -> Value { + match RpkixCrl::decode_der(bytes) { + Ok(crl) => json!({ + "type": "crl", + "decode": {"profileValid": true}, + "rawDer": bytes_summary(&crl.raw_der), + "version": crl.version, + "issuer": crl.issuer_dn, + "signatureAlgorithm": crl.signature_algorithm_oid, + "thisUpdate": format_time(crl.this_update.utc), + "nextUpdate": format_time(crl.next_update.utc), + "extensions": { + "authorityKeyIdentifier": hex::encode(&crl.extensions.authority_key_identifier), + "crlNumberHex": crl.extensions.crl_number.to_hex_upper(), + "crlNumber": crl.extensions.crl_number.to_u64(), + }, + "revokedCertificates": { + "count": crl.revoked_certs.len(), + "truncated": crl.revoked_certs.len() > entry_limit, + "entries": crl.revoked_certs.iter().take(entry_limit).map(|item| { + json!({ + "serialNumberHex": item.serial_number.to_hex_upper(), + "serialNumber": item.serial_number.to_u64(), + "revocationDate": format_time(item.revocation_date.utc), + }) + }).collect::>(), + }, + }), + Err(err) => json!({ + "type": "crl", + "decode": {"profileValid": false, "error": err.to_string()}, + }), + } +} + +pub fn manifest_file_entries_page( + bytes: &[u8], + offset: usize, + limit: usize, +) -> Result<(usize, Vec), String> { + let mft = ManifestObject::decode_der(bytes).map_err(|err| err.to_string())?; + let entries = mft.manifest.parse_files().map_err(|err| err.to_string())?; + let total = entries.len(); + let end = (offset + limit).min(total); + let page = entries[offset.min(total)..end] + .iter() + .map(|item| json!({"fileName": item.file_name, "hashHex": hex::encode(item.hash_bytes)})) + .collect::>(); + Ok((total, page)) +} + +pub fn crl_revoked_entries_page( + bytes: &[u8], + offset: usize, + limit: usize, +) -> Result<(usize, Vec), String> { + let crl = RpkixCrl::decode_der(bytes).map_err(|err| err.to_string())?; + let total = crl.revoked_certs.len(); + let end = (offset + limit).min(total); + let page = crl.revoked_certs[offset.min(total)..end] + .iter() + .map(|item| { + json!({ + "serialNumberHex": item.serial_number.to_hex_upper(), + "serialNumber": item.serial_number.to_u64(), + "revocationDate": format_time(item.revocation_date.utc), + }) + }) + .collect::>(); + Ok((total, page)) +} + +pub fn parse_roa_json(bytes: &[u8], entry_limit: usize) -> Value { + match RoaObject::decode_der(bytes) { + Ok(roa) => json!({ + "type": "roa", + "decode": {"profileValid": true}, + "eContentType": roa.econtent_type, + "signedObject": signed_object_json(&roa.signed_object), + "roa": { + "version": roa.roa.version, + "asId": roa.roa.as_id, + "ipAddressFamilies": roa.roa.ip_addr_blocks.iter().map(|family| { + json!({ + "afi": format!("{:?}", family.afi), + "addressCount": family.addresses.len(), + "truncated": family.addresses.len() > entry_limit, + "addresses": family.addresses.iter().take(entry_limit).map(|entry| { + json!({ + "prefix": roa_prefix_string(&entry.prefix), + "maxLength": entry.max_length, + }) + }).collect::>(), + }) + }).collect::>(), + }, + "embeddedEeProfile": result_json(roa.validate_embedded_ee_cert().map_err(|e| e.to_string())), + "cmsSignature": result_json(roa.signed_object.verify_signature().map_err(|e| e.to_string())), + }), + Err(err) => json!({ + "type": "roa", + "decode": {"profileValid": false, "error": err.to_string()}, + }), + } +} + +pub fn parse_aspa_json(bytes: &[u8], entry_limit: usize) -> Value { + match AspaObject::decode_der(bytes) { + Ok(aspa) => json!({ + "type": "aspa", + "decode": {"profileValid": true}, + "eContentType": aspa.econtent_type, + "signedObject": signed_object_json(&aspa.signed_object), + "aspa": { + "version": aspa.aspa.version, + "customerAsId": aspa.aspa.customer_as_id, + "providerCount": aspa.aspa.provider_as_ids.len(), + "providersTruncated": aspa.aspa.provider_as_ids.len() > entry_limit, + "providerAsIds": aspa.aspa.provider_as_ids.iter().take(entry_limit).copied().collect::>(), + }, + "embeddedEeProfile": result_json(aspa.validate_embedded_ee_cert().map_err(|e| e.to_string())), + "cmsSignature": result_json(aspa.signed_object.verify_signature().map_err(|e| e.to_string())), + }), + Err(err) => json!({ + "type": "aspa", + "decode": {"profileValid": false, "error": err.to_string()}, + }), + } +} + +fn resource_certificate_json(cert: &ResourceCertificate) -> Value { + let tbs = &cert.tbs; + json!({ + "rawDer": bytes_summary(&cert.raw_der), + "kind": format!("{:?}", cert.kind), + "version": tbs.version, + "serialNumberHex": hex::encode(tbs.serial_number.to_bytes_be()), + "signatureAlgorithm": tbs.signature_algorithm, + "issuer": tbs.issuer_name.to_string(), + "subject": tbs.subject_name.to_string(), + "validity": { + "notBefore": format_time(tbs.validity_not_before), + "notAfter": format_time(tbs.validity_not_after), + }, + "subjectPublicKeyInfo": bytes_summary(&tbs.subject_public_key_info), + "extensions": rc_extensions_json(&tbs.extensions), + }) +} + +fn rc_extensions_json(ext: &RcExtensions) -> Value { + json!({ + "basicConstraintsCa": ext.basic_constraints_ca, + "subjectKeyIdentifier": ext.subject_key_identifier.as_ref().map(hex::encode), + "authorityKeyIdentifier": ext.authority_key_identifier.as_ref().map(hex::encode), + "crlDistributionPointsUris": ext.crl_distribution_points_uris, + "caIssuersUris": ext.ca_issuers_uris, + "subjectInfoAccess": subject_info_access_json(ext.subject_info_access.as_ref()), + "certificatePoliciesOid": ext.certificate_policies_oid, + "ipResources": serde_json::to_value(&ext.ip_resources).unwrap_or(Value::Null), + "asResources": serde_json::to_value(&ext.as_resources).unwrap_or(Value::Null), + }) +} + +fn subject_info_access_json(value: Option<&SubjectInfoAccess>) -> Value { + match value { + None => Value::Null, + Some(SubjectInfoAccess::Ca(ca)) => json!({ + "kind": "ca", + "accessDescriptions": ca.access_descriptions.iter().map(access_description_json).collect::>(), + }), + Some(SubjectInfoAccess::Ee(ee)) => json!({ + "kind": "ee", + "signedObjectUris": ee.signed_object_uris, + "accessDescriptions": ee.access_descriptions.iter().map(access_description_json).collect::>(), + }), + } +} + +fn access_description_json(value: &AccessDescription) -> Value { + json!({ + "accessMethodOid": value.access_method_oid, + "accessLocation": value.access_location, + }) +} + +fn signed_object_json(signed_object: &RpkiSignedObject) -> Value { + let signed_data = &signed_object.signed_data; + json!({ + "rawDer": bytes_summary(&signed_object.raw_der), + "contentInfoContentType": signed_object.content_info_content_type, + "signedData": { + "version": signed_data.version, + "digestAlgorithms": signed_data.digest_algorithms, + "encapContentInfo": { + "eContentType": signed_data.encap_content_info.econtent_type, + "eContent": bytes_summary(&signed_data.encap_content_info.econtent), + }, + "certificates": signed_data.certificates.iter().map(ee_certificate_json).collect::>(), + "crlsPresent": signed_data.crls_present, + "signerInfos": signed_data.signer_infos.iter().map(signer_info_json).collect::>(), + }, + }) +} + +fn ee_certificate_json(cert: &ResourceEeCertificate) -> Value { + json!({ + "rawDer": bytes_summary(&cert.raw_der), + "subjectKeyIdentifier": hex::encode(&cert.subject_key_identifier), + "spkiDer": bytes_summary(&cert.spki_der), + "rsaPublicKey": { + "modulus": bytes_summary(&cert.rsa_public_modulus), + "exponent": bytes_summary(&cert.rsa_public_exponent), + }, + "tbsCertificate": bytes_summary(&cert.tbs_certificate_der), + "certificateSignature": bytes_summary(&cert.signature_bytes), + "keyUsageSummary": format!("{:?}", cert.key_usage_summary), + "siaSignedObjectUris": cert.sia_signed_object_uris, + "resourceCertificate": resource_certificate_json(&cert.resource_cert), + }) +} + +fn signer_info_json(info: &SignerInfoProfiled) -> Value { + json!({ + "version": info.version, + "sidSki": hex::encode(&info.sid_ski), + "digestAlgorithm": info.digest_algorithm, + "signatureAlgorithm": info.signature_algorithm, + "signedAttrs": signed_attrs_json(&info.signed_attrs), + "unsignedAttrsPresent": info.unsigned_attrs_present, + "signature": bytes_summary(&info.signature), + "signedAttrsDerForSignature": bytes_summary(&info.signed_attrs_der_for_signature), + }) +} + +fn signed_attrs_json(attrs: &SignedAttrsProfiled) -> Value { + json!({ + "contentType": attrs.content_type, + "messageDigest": hex::encode(&attrs.message_digest), + "signingTime": { + "utc": format_time(attrs.signing_time.utc), + "encoding": format!("{:?}", attrs.signing_time.encoding), + }, + "otherAttrsPresent": attrs.other_attrs_present, + }) +} + +fn result_json(result: Result<(), String>) -> Value { + match result { + Ok(()) => json!({"valid": true}), + Err(err) => json!({"valid": false, "error": err}), + } +} + +fn object_type_label(object_type: ObjectType) -> &'static str { + match object_type { + ObjectType::Auto => "auto", + ObjectType::Cer => "cer", + ObjectType::Mft => "mft", + ObjectType::Crl => "crl", + ObjectType::Roa => "roa", + ObjectType::Aspa => "aspa", + } +} + +fn bytes_summary(bytes: &[u8]) -> Value { + let head_len = bytes.len().min(16); + let tail_len = bytes.len().min(16); + json!({ + "len": bytes.len(), + "sha256": sha256_hex(bytes), + "headHex": hex::encode(&bytes[..head_len]), + "tailHex": hex::encode(&bytes[bytes.len().saturating_sub(tail_len)..]), + }) +} + +fn sha256_hex(bytes: &[u8]) -> String { + hex::encode(Sha256::digest(bytes)) +} + +fn format_time(value: time::OffsetDateTime) -> String { + value + .to_offset(time::UtcOffset::UTC) + .format(&time::format_description::well_known::Rfc3339) + .unwrap_or_else(|_| value.unix_timestamp().to_string()) +} + +fn roa_prefix_string(prefix: &RoaIpPrefix) -> String { + let bytes = prefix.addr_bytes(); + match prefix.afi { + RoaAfi::Ipv4 => { + let octets = [bytes[0], bytes[1], bytes[2], bytes[3]]; + format!("{}/{}", Ipv4Addr::from(octets), prefix.prefix_len) + } + RoaAfi::Ipv6 => { + let mut octets = [0u8; 16]; + octets.copy_from_slice(bytes); + format!("{}/{}", Ipv6Addr::from(octets), prefix.prefix_len) + } + } +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use super::*; + + #[test] + fn object_type_parser_and_resolver_cover_aliases() { + assert_eq!(ObjectType::parse("auto").unwrap(), ObjectType::Auto); + assert_eq!(ObjectType::parse(".cer").unwrap(), ObjectType::Cer); + assert_eq!(ObjectType::parse("certificate").unwrap(), ObjectType::Cer); + assert_eq!(ObjectType::parse("manifest").unwrap(), ObjectType::Mft); + assert_eq!(ObjectType::parse(".crl").unwrap(), ObjectType::Crl); + assert_eq!(ObjectType::parse("roa").unwrap(), ObjectType::Roa); + assert_eq!(ObjectType::parse("aspa").unwrap(), ObjectType::Aspa); + assert_eq!(ObjectType::parse(".asa").unwrap(), ObjectType::Aspa); + assert!(ObjectType::parse("unknown").is_err()); + assert_eq!(ObjectType::Aspa.label(), "aspa"); + + assert_eq!( + resolve_object_type(ObjectType::Auto, Path::new("repo/a.cer")).unwrap(), + ObjectType::Cer + ); + assert_eq!( + resolve_object_type(ObjectType::Auto, Path::new("repo/a.mft")).unwrap(), + ObjectType::Mft + ); + assert_eq!( + resolve_object_type(ObjectType::Auto, Path::new("repo/a.crl")).unwrap(), + ObjectType::Crl + ); + assert_eq!( + resolve_object_type(ObjectType::Auto, Path::new("repo/a.roa")).unwrap(), + ObjectType::Roa + ); + assert_eq!( + resolve_object_type(ObjectType::Auto, Path::new("repo/a.asa")).unwrap(), + ObjectType::Aspa + ); + assert_eq!( + resolve_object_type(ObjectType::Roa, Path::new("repo/a.bin")).unwrap(), + ObjectType::Roa + ); + assert!(resolve_object_type(ObjectType::Auto, Path::new("repo/a.bin")).is_err()); + } + + #[test] + fn invalid_der_returns_error_projection_for_all_object_types() { + let bytes = b"not der"; + for object_type in [ + ObjectType::Cer, + ObjectType::Mft, + ObjectType::Crl, + ObjectType::Roa, + ObjectType::Aspa, + ] { + let value = parse_object_json(object_type, Path::new("bad.der"), bytes, 1); + assert_eq!( + value["object"]["decode"]["profileValid"].as_bool(), + Some(false) + ); + assert!(value["object"]["decode"]["error"].as_str().is_some()); + } + + let record = build_object_projection(ObjectType::Auto, Path::new("bad.bin"), bytes, 1); + assert_eq!(record.object_type, "unknown"); + assert_eq!(record.parse_status, "error"); + assert!(record.error_summary.is_some()); + } + + #[test] + fn parses_synthetic_objects_into_human_readable_projection() { + let fixture = crate::test_support::synthetic_repository(); + let repository = fixture.case_repository("baseline-v1"); + let cases = [ + ( + ObjectType::Cer, + fixture.trust_anchor(), + "cer", + "resourceCertificate", + ), + ( + ObjectType::Mft, + repository.join("child/child.mft"), + "mft", + "manifest", + ), + ( + ObjectType::Crl, + repository.join("child/child.crl"), + "crl", + "revokedCertificates", + ), + ( + ObjectType::Roa, + repository.join("child/valid.roa"), + "roa", + "roa", + ), + ( + ObjectType::Aspa, + repository.join("child/valid.asa"), + "aspa", + "aspa", + ), + ]; + + for (object_type, path, expected_type, expected_section) in cases { + let bytes = std::fs::read(&path).expect("synthetic object"); + let record = build_object_projection(object_type, &path, &bytes, 1); + assert_eq!(record.object_type, expected_type); + assert_eq!(record.parse_status, "ok"); + assert_eq!( + record.projection["object"]["decode"]["profileValid"].as_bool(), + Some(true) + ); + assert!(record.projection["object"][expected_section].is_object()); + } + } + + #[test] + fn large_projection_lists_are_paged_from_raw_bytes() { + let repository = crate::test_support::synthetic_repository().case_repository("baseline-v1"); + let mft_bytes = std::fs::read(repository.join("child/child.mft")).expect("synthetic mft"); + let (total, page) = manifest_file_entries_page(&mft_bytes, 1, 3).expect("mft page"); + assert!(total >= 3); + assert_eq!(page.len(), (total - 1).min(3)); + assert!(page[0]["fileName"].as_str().is_some()); + let (_, empty_page) = + manifest_file_entries_page(&mft_bytes, total + 10, 3).expect("empty page"); + assert!(empty_page.is_empty()); + + let crl_bytes = std::fs::read(repository.join("child/child.crl")).expect("synthetic crl"); + let (total, page) = crl_revoked_entries_page(&crl_bytes, 0, 5).expect("crl page"); + assert!(page.len() <= total); + let (_, empty_page) = + crl_revoked_entries_page(&crl_bytes, total + 10, 5).expect("empty crl page"); + assert!(empty_page.is_empty()); + } +} diff --git a/src/model/rc.rs b/src/model/rc.rs new file mode 100644 index 0000000..12ef83a --- /dev/null +++ b/src/model/rc.rs @@ -0,0 +1,30 @@ +use der_parser::ber::{BerObjectContent, Class}; +use der_parser::der::{DerObject, Tag, parse_der}; +use der_parser::num_bigint::BigUint; +use x509_parser::asn1_rs::{Class as Asn1Class, Tag as Asn1Tag}; +use x509_parser::extensions::ParsedExtension; +use x509_parser::prelude::{FromDer, X509Certificate, X509Extension, X509Version}; + +use serde::{Deserialize, Serialize}; + +use crate::model::common::{ + Asn1TimeUtc, DerReader, InvalidTimeEncodingError, UtcTime, X509NameDer, asn1_time_to_model, +}; +use crate::model::oid::{ + OID_AD_CA_ISSUERS_RAW, OID_AD_CA_REPOSITORY, OID_AD_CA_REPOSITORY_RAW, OID_AD_RPKI_MANIFEST, + OID_AD_RPKI_MANIFEST_RAW, OID_AD_RPKI_NOTIFY, OID_AD_RPKI_NOTIFY_RAW, OID_AD_SIGNED_OBJECT, + OID_AD_SIGNED_OBJECT_RAW, OID_AUTHORITY_INFO_ACCESS, OID_AUTHORITY_INFO_ACCESS_RAW, + OID_AUTHORITY_KEY_IDENTIFIER, OID_AUTHORITY_KEY_IDENTIFIER_RAW, OID_AUTONOMOUS_SYS_IDS, + OID_AUTONOMOUS_SYS_IDS_RAW, OID_BASIC_CONSTRAINTS, OID_BASIC_CONSTRAINTS_RAW, + OID_CERTIFICATE_POLICIES, OID_CERTIFICATE_POLICIES_RAW, OID_CP_IPADDR_ASNUMBER, + OID_CP_IPADDR_ASNUMBER_RAW, OID_CRL_DISTRIBUTION_POINTS, OID_CRL_DISTRIBUTION_POINTS_RAW, + OID_EXTENDED_KEY_USAGE, OID_IP_ADDR_BLOCKS, OID_IP_ADDR_BLOCKS_RAW, OID_KEY_USAGE, OID_QT_CPS, + OID_SHA256_WITH_RSA_ENCRYPTION, OID_SHA256_WITH_RSA_ENCRYPTION_RAW, OID_SUBJECT_INFO_ACCESS, + OID_SUBJECT_INFO_ACCESS_RAW, OID_SUBJECT_KEY_IDENTIFIER, OID_SUBJECT_KEY_IDENTIFIER_RAW, +}; + +include!("rc/types.rs"); +include!("rc/certificate_validation.rs"); +include!("rc/parsed_validation.rs"); +include!("rc/parsing.rs"); +include!("rc/strict_name_tests.rs"); diff --git a/src/model/rc/certificate_validation.rs b/src/model/rc/certificate_validation.rs new file mode 100644 index 0000000..2ab3e7f --- /dev/null +++ b/src/model/rc/certificate_validation.rs @@ -0,0 +1,248 @@ +// Resource certificate profile validation and strict-name checks. + +impl ResourceCertificate { + /// Parse step of scheme A (`parse → validate → verify`). + pub fn parse_der( + der: &[u8], + ) -> Result { + let (rem, cert) = X509Certificate::from_der(der) + .map_err(|e| ResourceCertificateParseError::Parse(e.to_string()))?; + if !rem.is_empty() { + return Err(ResourceCertificateParseError::TrailingBytes(rem.len())); + } + + let validity_not_before = asn1_time_to_model(cert.validity().not_before); + let validity_not_after = asn1_time_to_model(cert.validity().not_after); + + let subject_public_key_info = cert.tbs_certificate.subject_pki.raw.to_vec(); + + let signature_algorithm = algorithm_identifier_value(&cert.signature_algorithm); + let tbs_signature_algorithm = algorithm_identifier_value(&cert.tbs_certificate.signature); + let extensions = parse_extensions_parse(cert.extensions())?; + + Ok(ResourceCertificateParsed { + raw_der: der.to_vec(), + version: cert.version(), + serial_number: cert.tbs_certificate.serial.clone(), + signature_algorithm, + tbs_signature_algorithm, + issuer_name: X509NameDer(cert.issuer().as_raw().to_vec()), + subject_name: X509NameDer(cert.subject().as_raw().to_vec()), + validity_not_before, + validity_not_after, + subject_public_key_info, + extensions, + }) + } + + /// Profile validate step of scheme A (`parse → validate → verify`). + /// + /// `ResourceCertificate` is already profile-validated when constructed via `decode_der()` / + /// `ResourceCertificateParsed::validate_profile()`. + pub fn validate_profile(&self) -> Result<(), ResourceCertificateProfileError> { + Ok(()) + } + + pub fn validate_rfc6487_profile( + &self, + role: ResourceCertificateRole, + ) -> Result<(), ResourceCertificateProfileError> { + let role_name = match role { + ResourceCertificateRole::TrustAnchor => "trust anchor CA", + ResourceCertificateRole::Ca => "CA", + ResourceCertificateRole::SignedObjectEe => "signed-object EE", + ResourceCertificateRole::RouterEe => "router EE", + }; + let ca_role = matches!( + role, + ResourceCertificateRole::TrustAnchor | ResourceCertificateRole::Ca + ); + + if ca_role { + let constraints = self + .tbs + .extensions + .basic_constraints + .as_ref() + .ok_or(ResourceCertificateProfileError::BasicConstraintsMissing)?; + if !constraints.critical { + return Err(ResourceCertificateProfileError::BasicConstraintsCriticality); + } + if !constraints.ca { + return Err(ResourceCertificateProfileError::BasicConstraintsCaFalse); + } + if constraints.path_len_constraint.is_some() { + return Err(ResourceCertificateProfileError::BasicConstraintsPathLenPresent); + } + } else if self.tbs.extensions.basic_constraints.is_some() { + return Err(ResourceCertificateProfileError::BasicConstraintsEeMustOmit); + } + + for oid in &self.tbs.extensions.extension_oids { + if !is_permitted_extension(oid, role) { + return Err(ResourceCertificateProfileError::DisallowedExtension { + role: role_name, + oid: oid.clone(), + }); + } + } + + let policies = self + .tbs + .extensions + .certificate_policies + .as_ref() + .ok_or(ResourceCertificateProfileError::CertificatePoliciesMissing)?; + if policies.policy_oid != OID_CP_IPADDR_ASNUMBER { + return Err(ResourceCertificateProfileError::InvalidCertificatePolicy( + policies.policy_oid.clone(), + )); + } + if policies.qualifier_oids.len() > 1 { + return Err(ResourceCertificateProfileError::CertificatePoliciesTooManyQualifiers); + } + if let Some(qualifier_oid) = policies.qualifier_oids.first() + && qualifier_oid != OID_QT_CPS { + return Err( + ResourceCertificateProfileError::CertificatePoliciesInvalidQualifier( + qualifier_oid.clone(), + ), + ); + } + + if self + .tbs + .extensions + .as_resources + .as_ref() + .is_some_and(|resources| resources.rdi.is_some()) + { + return Err(ResourceCertificateProfileError::AsResourcesRdiPresent); + } + + Ok(()) + } + + pub fn validate_strict_name_profile(&self) -> Result<(), ResourceCertificateProfileError> { + validate_strict_rpki_name(&self.tbs.issuer_name, "issuer")?; + validate_strict_rpki_name(&self.tbs.subject_name, "subject")?; + Ok(()) + } + + /// Decode a resource certificate (`parse + validate`). + pub fn decode_der(der: &[u8]) -> Result { + Ok(Self::parse_der(der)?.validate_profile()?) + } + + pub fn decode_der_with_strict_name(der: &[u8]) -> Result { + let cert = Self::decode_der(der)?; + cert.validate_strict_name_profile()?; + Ok(cert) + } + + /// Backwards-compatible helper (historical name). + pub fn from_der(der: &[u8]) -> Result { + Self::decode_der(der) + } +} + +fn is_permitted_extension(oid: &str, role: ResourceCertificateRole) -> bool { + matches!( + oid, + OID_BASIC_CONSTRAINTS + | OID_KEY_USAGE + | OID_SUBJECT_KEY_IDENTIFIER + | OID_AUTHORITY_KEY_IDENTIFIER + | OID_CRL_DISTRIBUTION_POINTS + | OID_AUTHORITY_INFO_ACCESS + | OID_SUBJECT_INFO_ACCESS + | OID_CERTIFICATE_POLICIES + | OID_IP_ADDR_BLOCKS + | OID_AUTONOMOUS_SYS_IDS + ) || (role == ResourceCertificateRole::RouterEe && oid == OID_EXTENDED_KEY_USAGE) +} + +fn validate_strict_rpki_name( + name: &X509NameDer, + role: &'static str, +) -> Result<(), ResourceCertificateProfileError> { + let mut name_seq = DerReader::new(name.as_raw()) + .take_sequence() + .map_err(|e| ResourceCertificateProfileError::StrictName { role, detail: e })?; + + let mut common_name_count = 0usize; + let mut serial_number_count = 0usize; + + while !name_seq.is_empty() { + let set_bytes = name_seq + .take_tag(0x31) + .map_err(|e| ResourceCertificateProfileError::StrictName { role, detail: e })?; + let mut rdn_set = DerReader::new(set_bytes); + if rdn_set.is_empty() { + return Err(ResourceCertificateProfileError::StrictName { + role, + detail: "RelativeDistinguishedName SET is empty".to_string(), + }); + } + + while !rdn_set.is_empty() { + let mut attr = rdn_set + .take_sequence() + .map_err(|e| ResourceCertificateProfileError::StrictName { role, detail: e })?; + let oid = attr + .take_tag(0x06) + .map_err(|e| ResourceCertificateProfileError::StrictName { role, detail: e })?; + let (value_tag, _value) = attr + .take_any() + .map_err(|e| ResourceCertificateProfileError::StrictName { role, detail: e })?; + if !attr.is_empty() { + return Err(ResourceCertificateProfileError::StrictName { + role, + detail: "AttributeTypeAndValue must be SEQUENCE of 2".to_string(), + }); + } + + match *oid { + // 2.5.4.3 commonName + [0x55, 0x04, 0x03] => { + common_name_count += 1; + if value_tag != 0x13 { + return Err(ResourceCertificateProfileError::StrictName { + role, + detail: format!( + "commonName must be PrintableString, got tag 0x{value_tag:02X}" + ), + }); + } + } + // 2.5.4.5 serialNumber + [0x55, 0x04, 0x05] => { + serial_number_count += 1; + if value_tag != 0x13 { + return Err(ResourceCertificateProfileError::StrictName { + role, + detail: format!( + "serialNumber must be PrintableString, got tag 0x{value_tag:02X}" + ), + }); + } + } + _ => {} + } + } + } + + if common_name_count != 1 { + return Err(ResourceCertificateProfileError::StrictName { + role, + detail: format!("commonName must appear exactly once, got {common_name_count}"), + }); + } + if serial_number_count > 1 { + return Err(ResourceCertificateProfileError::StrictName { + role, + detail: format!("serialNumber must appear at most once, got {serial_number_count}"), + }); + } + Ok(()) +} diff --git a/src/model/rc/parsed_validation.rs b/src/model/rc/parsed_validation.rs new file mode 100644 index 0000000..6f04803 --- /dev/null +++ b/src/model/rc/parsed_validation.rs @@ -0,0 +1,346 @@ +// Parsed certificate and extension profile validation. + +impl ResourceCertificateParsed { + pub fn validate_profile(self) -> Result { + let version = match self.version { + X509Version::V3 => 2u32, + _ => return Err(ResourceCertificateProfileError::InvalidVersion), + }; + + self.validity_not_before + .validate_encoding_rfc5280("notBefore")?; + self.validity_not_after + .validate_encoding_rfc5280("notAfter")?; + + if self.signature_algorithm != self.tbs_signature_algorithm { + return Err(ResourceCertificateProfileError::SignatureAlgorithmMismatch); + } + if self.signature_algorithm.oid != OID_SHA256_WITH_RSA_ENCRYPTION { + return Err(ResourceCertificateProfileError::UnsupportedSignatureAlgorithm); + } + if !self.signature_algorithm.params_absent_or_null() { + return Err(ResourceCertificateProfileError::InvalidSignatureAlgorithmParameters); + } + + let is_self_signed = self.issuer_name == self.subject_name; + let extensions = self.extensions.validate_profile(is_self_signed)?; + let kind = if extensions.basic_constraints_ca { + ResourceCertKind::Ca + } else { + ResourceCertKind::Ee + }; + + Ok(ResourceCertificate { + raw_der: self.raw_der, + tbs: RpkixTbsCertificate { + version, + serial_number: self.serial_number, + signature_algorithm: self.signature_algorithm.oid, + issuer_name: self.issuer_name, + subject_name: self.subject_name, + validity_not_before: self.validity_not_before.utc, + validity_not_after: self.validity_not_after.utc, + subject_public_key_info: self.subject_public_key_info, + extensions, + }, + kind, + }) + } +} + +impl RcExtensionsParsed { + pub fn validate_profile( + self, + is_self_signed: bool, + ) -> Result { + // NOTE(perf): `self` is consumed. Prefer moving decoded fields out rather than cloning, + // especially for large resource sets and URI lists. + let RcExtensionsParsed { + basic_constraints, + subject_key_identifier, + authority_key_identifier, + crl_distribution_points, + authority_info_access, + subject_info_access, + certificate_policies, + extension_oids, + ip_resources, + as_resources, + } = self; + + if basic_constraints.len() > 1 { + return Err(ResourceCertificateProfileError::DuplicateExtension( + "basicConstraints", + )); + } + let basic_constraints = basic_constraints.into_iter().next(); + let basic_constraints_ca = basic_constraints.as_ref().is_some_and(|bc| bc.ca); + + let subject_key_identifier = match subject_key_identifier.len() { + 0 => None, + 1 => { + let (ski, critical) = subject_key_identifier.into_iter().next().expect("len==1"); + if critical { + return Err(ResourceCertificateProfileError::SkiCriticality); + } + Some(ski) + } + _ => { + return Err(ResourceCertificateProfileError::DuplicateExtension( + "subjectKeyIdentifier", + )); + } + }; + + let authority_key_identifier = match authority_key_identifier.len() { + 0 => { + if is_self_signed { + None + } else { + return Err(ResourceCertificateProfileError::AkiMissing); + } + } + 1 => { + let (aki, critical) = authority_key_identifier.into_iter().next().expect("len==1"); + if critical { + return Err(ResourceCertificateProfileError::AkiCriticality); + } + if aki.has_authority_cert_issuer { + return Err(ResourceCertificateProfileError::AkiAuthorityCertIssuerPresent); + } + if aki.has_authority_cert_serial { + return Err(ResourceCertificateProfileError::AkiAuthorityCertSerialPresent); + } + let keyid = aki.key_identifier; + if is_self_signed { + if let (Some(keyid), Some(ski)) = + (keyid.as_ref(), subject_key_identifier.as_ref()) + && keyid != ski { + return Err(ResourceCertificateProfileError::AkiSelfSignedNotEqualSki); + } + } else if keyid.is_none() { + return Err(ResourceCertificateProfileError::AkiMissing); + } + keyid + } + _ => { + return Err(ResourceCertificateProfileError::DuplicateExtension( + "authorityKeyIdentifier", + )); + } + }; + + let crl_distribution_points_uris = match crl_distribution_points.len() { + 0 => { + if is_self_signed { + None + } else { + return Err(ResourceCertificateProfileError::CrlDistributionPointsMissing); + } + } + 1 => { + let (crldp, critical) = crl_distribution_points.into_iter().next().expect("len==1"); + if critical { + return Err(ResourceCertificateProfileError::CrlDistributionPointsCriticality); + } + if is_self_signed { + return Err( + ResourceCertificateProfileError::CrlDistributionPointsSelfSignedMustOmit, + ); + } + if crldp.distribution_points.len() != 1 { + return Err(ResourceCertificateProfileError::CrlDistributionPointsNotSingle); + } + let dp = crldp + .distribution_points + .into_iter() + .next() + .expect("len==1"); + if dp.reasons_present { + return Err(ResourceCertificateProfileError::CrlDistributionPointsHasReasons); + } + if dp.crl_issuer_present { + return Err(ResourceCertificateProfileError::CrlDistributionPointsHasCrlIssuer); + } + if !dp.distribution_point_present { + return Err( + ResourceCertificateProfileError::CrlDistributionPointsNoDistributionPoint, + ); + } + if dp.name_relative_to_crl_issuer_present || !dp.full_name_present { + return Err(ResourceCertificateProfileError::CrlDistributionPointsInvalidName); + } + if dp.full_name_not_uri { + return Err( + ResourceCertificateProfileError::CrlDistributionPointsFullNameNotUri, + ); + } + if !dp.full_name_uris.iter().any(|u| u.starts_with("rsync://")) { + return Err(ResourceCertificateProfileError::CrlDistributionPointsNoRsync); + } + Some(dp.full_name_uris) + } + _ => { + return Err(ResourceCertificateProfileError::DuplicateExtension( + "cRLDistributionPoints", + )); + } + }; + + let ca_issuers_uris = match authority_info_access.len() { + 0 => { + if is_self_signed { + None + } else { + return Err(ResourceCertificateProfileError::AuthorityInfoAccessMissing); + } + } + 1 => { + let (aia, critical) = authority_info_access.into_iter().next().expect("len==1"); + if critical { + return Err(ResourceCertificateProfileError::AuthorityInfoAccessCriticality); + } + if is_self_signed { + return Err( + ResourceCertificateProfileError::AuthorityInfoAccessSelfSignedMustOmit, + ); + } + if aia.ca_issuers_access_location_not_uri { + return Err( + ResourceCertificateProfileError::AuthorityInfoAccessCaIssuersNotUri, + ); + } + if aia.ca_issuers_uris.is_empty() { + return Err( + ResourceCertificateProfileError::AuthorityInfoAccessMissingCaIssuers, + ); + } + if !aia + .ca_issuers_uris + .iter() + .any(|u| u.starts_with("rsync://")) + { + return Err(ResourceCertificateProfileError::AuthorityInfoAccessNoRsync); + } + Some(aia.ca_issuers_uris) + } + _ => { + return Err(ResourceCertificateProfileError::DuplicateExtension( + "authorityInfoAccess", + )); + } + }; + + let subject_info_access = match subject_info_access.len() { + 0 => None, + 1 => { + let (sia, critical) = subject_info_access.into_iter().next().expect("len==1"); + if critical { + return Err(ResourceCertificateProfileError::SiaCriticality); + } + if sia.signed_object_access_location_not_uri { + return Err(ResourceCertificateProfileError::SignedObjectSiaNotUri); + } + if !sia.signed_object_uris.is_empty() + && !sia + .signed_object_uris + .iter() + .any(|u| u.starts_with("rsync://")) + { + return Err(ResourceCertificateProfileError::SignedObjectSiaNoRsync); + } + if sia.signed_object_uris.is_empty() { + Some(SubjectInfoAccess::Ca(SubjectInfoAccessCa { + access_descriptions: sia.access_descriptions, + })) + } else { + Some(SubjectInfoAccess::Ee(SubjectInfoAccessEe { + signed_object_uris: sia.signed_object_uris, + access_descriptions: sia.access_descriptions, + })) + } + } + _ => { + return Err(ResourceCertificateProfileError::DuplicateExtension( + "subjectInfoAccess", + )); + } + }; + + let certificate_policies = match certificate_policies.len() { + 0 => None, + 1 => { + let (policies, critical) = certificate_policies.into_iter().next().expect("len==1"); + if !critical { + return Err(ResourceCertificateProfileError::CertificatePoliciesCriticality); + } + if policies.len() != 1 { + return Err(ResourceCertificateProfileError::InvalidCertificatePolicy( + "expected exactly one policy".into(), + )); + } + let policy = policies.into_iter().next().expect("len==1"); + if policy.policy_oid != OID_CP_IPADDR_ASNUMBER { + return Err(ResourceCertificateProfileError::InvalidCertificatePolicy( + policy.policy_oid, + )); + } + Some(policy) + } + _ => { + return Err(ResourceCertificateProfileError::DuplicateExtension( + "certificatePolicies", + )); + } + }; + + let ip_resources = match ip_resources.len() { + 0 => None, + 1 => { + let (ip, critical) = ip_resources.into_iter().next().expect("len==1"); + if !critical { + return Err(ResourceCertificateProfileError::IpResourcesCriticality); + } + Some(ip) + } + _ => { + return Err(ResourceCertificateProfileError::DuplicateExtension( + "ipAddrBlocks", + )); + } + }; + + let as_resources = match as_resources.len() { + 0 => None, + 1 => { + let (asn, critical) = as_resources.into_iter().next().expect("len==1"); + if !critical { + return Err(ResourceCertificateProfileError::AsResourcesCriticality); + } + Some(asn) + } + _ => { + return Err(ResourceCertificateProfileError::DuplicateExtension( + "autonomousSysIds", + )); + } + }; + + Ok(RcExtensions { + basic_constraints_ca, + basic_constraints, + subject_key_identifier, + authority_key_identifier, + crl_distribution_points_uris, + ca_issuers_uris, + subject_info_access, + certificate_policies_oid: certificate_policies + .as_ref() + .map(|_| OID_CP_IPADDR_ASNUMBER.to_string()), + certificate_policies, + extension_oids, + ip_resources, + as_resources, + }) + } +} diff --git a/src/model/rc/parsing.rs b/src/model/rc/parsing.rs new file mode 100644 index 0000000..29ddc48 --- /dev/null +++ b/src/model/rc/parsing.rs @@ -0,0 +1,584 @@ +// DER parsing helpers for certificate extensions and resources. + +fn algorithm_identifier_value( + ai: &x509_parser::x509::AlgorithmIdentifier<'_>, +) -> AlgorithmIdentifierValue { + let parameters = ai.parameters.as_ref().map(|p| AlgorithmParametersValue { + class: p.class(), + tag: p.tag(), + data: p.as_bytes().to_vec(), + }); + // NOTE(perf): Avoid `to_id_string()` allocations for the algorithms we expect + // in RPKI resource certificates. Fall back to `to_id_string()` for unexpected + // algorithms (mostly error paths). + let oid = if ai.algorithm.as_bytes() == OID_SHA256_WITH_RSA_ENCRYPTION_RAW { + OID_SHA256_WITH_RSA_ENCRYPTION.to_string() + } else { + ai.algorithm.to_id_string() + }; + AlgorithmIdentifierValue { oid, parameters } +} + +fn parse_extensions_parse( + exts: &[X509Extension<'_>], +) -> Result { + let mut basic_constraints: Vec = Vec::new(); + let mut ski: Vec<(Vec, bool)> = Vec::new(); + let mut aki: Vec<(AuthorityKeyIdentifierParsed, bool)> = Vec::new(); + let mut crldp: Vec<(CrlDistributionPointsParsed, bool)> = Vec::new(); + let mut aia: Vec<(AuthorityInfoAccessParsed, bool)> = Vec::new(); + let mut sia: Vec<(SubjectInfoAccessParsed, bool)> = Vec::new(); + let mut cert_policies: Vec<(Vec, bool)> = Vec::new(); + let mut extension_oids: Vec = Vec::with_capacity(exts.len()); + + let mut ip_resources: Vec<(IpResourceSet, bool)> = Vec::new(); + let mut as_resources: Vec<(AsResourceSet, bool)> = Vec::new(); + + for ext in exts { + let oid = ext.oid.as_bytes(); + extension_oids.push(ext.oid.to_id_string()); + if oid == OID_BASIC_CONSTRAINTS_RAW { + let ParsedExtension::BasicConstraints(bc) = ext.parsed_extension() else { + return Err(ResourceCertificateParseError::Parse( + "basicConstraints parse failed".into(), + )); + }; + basic_constraints.push(BasicConstraintsProfile { + ca: bc.ca, + critical: ext.critical, + path_len_constraint: bc.path_len_constraint, + }); + } else if oid == OID_SUBJECT_KEY_IDENTIFIER_RAW { + let ParsedExtension::SubjectKeyIdentifier(s) = ext.parsed_extension() else { + return Err(ResourceCertificateParseError::Parse( + "subjectKeyIdentifier parse failed".into(), + )); + }; + ski.push((s.0.to_vec(), ext.critical)); + } else if oid == OID_AUTHORITY_KEY_IDENTIFIER_RAW { + let ParsedExtension::AuthorityKeyIdentifier(a) = ext.parsed_extension() else { + return Err(ResourceCertificateParseError::Parse( + "authorityKeyIdentifier parse failed".into(), + )); + }; + aki.push(( + AuthorityKeyIdentifierParsed { + key_identifier: a.key_identifier.as_ref().map(|k| k.0.to_vec()), + has_authority_cert_issuer: a.authority_cert_issuer.is_some(), + has_authority_cert_serial: a.authority_cert_serial.is_some(), + }, + ext.critical, + )); + } else if oid == OID_CRL_DISTRIBUTION_POINTS_RAW { + let ParsedExtension::CRLDistributionPoints(p) = ext.parsed_extension() else { + return Err(ResourceCertificateParseError::Parse( + "cRLDistributionPoints parse failed".into(), + )); + }; + crldp.push((parse_crldp_parse(p)?, ext.critical)); + } else if oid == OID_AUTHORITY_INFO_ACCESS_RAW { + let ParsedExtension::AuthorityInfoAccess(p) = ext.parsed_extension() else { + return Err(ResourceCertificateParseError::Parse( + "authorityInfoAccess parse failed".into(), + )); + }; + aia.push((parse_aia_parse(p.accessdescs.as_slice())?, ext.critical)); + } else if oid == OID_SUBJECT_INFO_ACCESS_RAW { + let ParsedExtension::SubjectInfoAccess(s) = ext.parsed_extension() else { + return Err(ResourceCertificateParseError::Parse( + "subjectInfoAccess parse failed".into(), + )); + }; + sia.push((parse_sia_parse(s.accessdescs.as_slice())?, ext.critical)); + } else if oid == OID_CERTIFICATE_POLICIES_RAW { + let ParsedExtension::CertificatePolicies(cp) = ext.parsed_extension() else { + return Err(ResourceCertificateParseError::Parse( + "certificatePolicies parse failed".into(), + )); + }; + let mut policies: Vec = Vec::with_capacity(cp.len()); + for p in cp.iter() { + let b = p.policy_id.as_bytes(); + let policy_oid = if b == OID_CP_IPADDR_ASNUMBER_RAW { + OID_CP_IPADDR_ASNUMBER.to_string() + } else { + p.policy_id.to_id_string() + }; + let qualifier_oids = p + .policy_qualifiers + .as_ref() + .map(|qualifiers| { + qualifiers + .iter() + .map(|qualifier| qualifier.policy_qualifier_id.to_id_string()) + .collect() + }) + .unwrap_or_default(); + policies.push(CertificatePoliciesProfile { + policy_oid, + qualifier_oids, + }); + } + cert_policies.push((policies, ext.critical)); + } else if oid == OID_IP_ADDR_BLOCKS_RAW { + let parsed = IpResourceSet::decode_extn_value(ext.value) + .map_err(|_e| ResourceCertificateParseError::InvalidIpResourcesEncoding)?; + ip_resources.push((parsed, ext.critical)); + } else if oid == OID_AUTONOMOUS_SYS_IDS_RAW { + let parsed = AsResourceSet::decode_extn_value(ext.value) + .map_err(|_e| ResourceCertificateParseError::InvalidAsResourcesEncoding)?; + as_resources.push((parsed, ext.critical)); + } + } + + Ok(RcExtensionsParsed { + basic_constraints, + subject_key_identifier: ski, + authority_key_identifier: aki, + crl_distribution_points: crldp, + authority_info_access: aia, + subject_info_access: sia, + certificate_policies: cert_policies, + extension_oids, + ip_resources, + as_resources, + }) +} + +fn parse_aia_parse( + access: &[x509_parser::extensions::AccessDescription<'_>], +) -> Result { + let mut ca_issuers_uris: Vec = Vec::new(); + let mut ca_issuers_access_location_not_uri = false; + + for ad in access { + if ad.access_method.as_bytes() != OID_AD_CA_ISSUERS_RAW { + continue; + } + let uri = match &ad.access_location { + x509_parser::extensions::GeneralName::URI(u) => u, + _ => { + ca_issuers_access_location_not_uri = true; + continue; + } + }; + ca_issuers_uris.push(uri.to_string()); + } + + Ok(AuthorityInfoAccessParsed { + ca_issuers_uris, + ca_issuers_access_location_not_uri, + }) +} + +fn parse_crldp_parse( + crldp: &x509_parser::extensions::CRLDistributionPoints<'_>, +) -> Result { + let mut out: Vec = Vec::new(); + for p in crldp.iter() { + let mut full_name_uris: Vec = Vec::new(); + let mut full_name_not_uri = false; + let mut full_name_present = false; + let mut name_relative_to_crl_issuer_present = false; + let mut distribution_point_present = false; + + if let Some(dp) = &p.distribution_point { + distribution_point_present = true; + match dp { + x509_parser::extensions::DistributionPointName::FullName(names) => { + full_name_present = true; + for n in names { + match n { + x509_parser::extensions::GeneralName::URI(u) => { + full_name_uris.push(u.to_string()); + } + _ => { + full_name_not_uri = true; + } + } + } + } + x509_parser::extensions::DistributionPointName::NameRelativeToCRLIssuer(_) => { + name_relative_to_crl_issuer_present = true; + } + } + } + + out.push(CrlDistributionPointParsed { + distribution_point_present, + reasons_present: p.reasons.is_some(), + crl_issuer_present: p.crl_issuer.is_some(), + name_relative_to_crl_issuer_present, + full_name_uris, + full_name_not_uri, + full_name_present, + }); + } + Ok(CrlDistributionPointsParsed { + distribution_points: out, + }) +} + +fn parse_sia_parse( + access: &[x509_parser::extensions::AccessDescription<'_>], +) -> Result { + let mut all = Vec::with_capacity(access.len()); + let mut signed_object_uris: Vec = Vec::new(); + let mut signed_object_access_location_not_uri = false; + + for ad in access { + let access_method_oid = if ad.access_method.as_bytes() == OID_AD_CA_REPOSITORY_RAW { + OID_AD_CA_REPOSITORY.to_string() + } else if ad.access_method.as_bytes() == OID_AD_RPKI_MANIFEST_RAW { + OID_AD_RPKI_MANIFEST.to_string() + } else if ad.access_method.as_bytes() == OID_AD_RPKI_NOTIFY_RAW { + OID_AD_RPKI_NOTIFY.to_string() + } else if ad.access_method.as_bytes() == OID_AD_SIGNED_OBJECT_RAW { + OID_AD_SIGNED_OBJECT.to_string() + } else { + ad.access_method.to_id_string() + }; + let is_signed_object = access_method_oid == OID_AD_SIGNED_OBJECT; + let uri = match &ad.access_location { + x509_parser::extensions::GeneralName::URI(u) => u, + _ => { + if is_signed_object { + signed_object_access_location_not_uri = true; + } + continue; + } + }; + if is_signed_object { + signed_object_uris.push(uri.to_string()); + } + all.push(AccessDescription { + access_method_oid, + access_location: uri.to_string(), + }); + } + + Ok(SubjectInfoAccessParsed { + access_descriptions: all, + signed_object_uris, + signed_object_access_location_not_uri, + }) +} + +fn parse_ip_addr_blocks(ext_value: &[u8]) -> Result { + let (rem, obj) = parse_der(ext_value).map_err(|_| ())?; + if !rem.is_empty() { + return Err(()); + } + let seq = obj.as_sequence().map_err(|_| ())?; + let mut families = Vec::with_capacity(seq.len()); + for fam in seq { + let fam_seq = fam.as_sequence().map_err(|_| ())?; + if fam_seq.len() != 2 { + return Err(()); + } + let af_bytes = fam_seq[0].as_slice().map_err(|_| ())?; + if af_bytes.len() != 2 { + return Err(()); + } + let afi = match af_bytes { + [0x00, 0x01] => Afi::Ipv4, + [0x00, 0x02] => Afi::Ipv6, + _ => return Err(()), + }; + + let choice = match &fam_seq[1].content { + BerObjectContent::Null => IpAddressChoice::Inherit, + BerObjectContent::Sequence(_) => { + let items_seq = fam_seq[1].as_sequence().map_err(|_| ())?; + let mut items = Vec::with_capacity(items_seq.len()); + for item in items_seq { + items.push(parse_ip_address_or_range(afi, item)?); + } + IpAddressChoice::AddressesOrRanges(items) + } + _ => return Err(()), + }; + families.push(IpAddressFamily { afi, choice }); + } + Ok(IpResourceSet { families }) +} + +fn parse_ip_address_or_range(afi: Afi, obj: &DerObject<'_>) -> Result { + match &obj.content { + BerObjectContent::BitString(_, _) => { + Ok(IpAddressOrRange::Prefix(parse_ip_prefix(afi, obj)?)) + } + BerObjectContent::Sequence(_) => { + let seq = obj.as_sequence().map_err(|_| ())?; + if seq.len() != 2 { + return Err(()); + } + let min = parse_ip_address_bound(afi, &seq[0], false)?; + let max = parse_ip_address_bound(afi, &seq[1], true)?; + Ok(IpAddressOrRange::Range(IpAddressRange { min, max })) + } + _ => Err(()), + } +} + +fn parse_ip_prefix(afi: Afi, obj: &DerObject<'_>) -> Result { + let (unused_bits, bytes) = match &obj.content { + BerObjectContent::BitString(unused, bso) => (*unused, bso.data.to_vec()), + _ => return Err(()), + }; + if unused_bits > 7 { + return Err(()); + } + if !bytes.is_empty() && unused_bits != 0 { + let mask = (1u8 << unused_bits) - 1; + if (bytes[bytes.len() - 1] & mask) != 0 { + return Err(()); + } + } else if bytes.is_empty() && unused_bits != 0 { + return Err(()); + } + let prefix_len = (bytes.len() * 8) + .checked_sub(unused_bits as usize) + .ok_or(())? as u16; + if prefix_len > afi.ub() { + return Err(()); + } + let addr = canonicalize_prefix_addr(afi, prefix_len, &bytes); + Ok(IpPrefix { + afi, + prefix_len, + addr, + }) +} + +/// Parse an RFC 3779 `IPAddress` BIT STRING into an address-like byte array. +/// +/// When used as an `IPAddressRange` endpoint, RFC 3779 allows endpoints to be encoded with +/// fewer than `ub` bits. In that case, the missing bits are interpreted as 0s for the lower +/// bound and 1s for the upper bound. This is essential to correctly interpret ranges that +/// are expressed on non-octet boundaries. +fn parse_ip_address_bound( + afi: Afi, + obj: &DerObject<'_>, + fill_remaining_ones: bool, +) -> Result, ()> { + let (unused_bits, bytes) = match &obj.content { + BerObjectContent::BitString(unused, bso) => (*unused, bso.data.to_vec()), + _ => return Err(()), + }; + if unused_bits > 7 { + return Err(()); + } + if !bytes.is_empty() && unused_bits != 0 { + let mask = (1u8 << unused_bits) - 1; + if (bytes[bytes.len() - 1] & mask) != 0 { + return Err(()); + } + } else if bytes.is_empty() && unused_bits != 0 { + return Err(()); + } + + let bit_len: u16 = (bytes.len() * 8) + .checked_sub(unused_bits as usize) + .ok_or(())? + .try_into() + .map_err(|_| ())?; + if bit_len > afi.ub() { + return Err(()); + } + + let mut out = vec![0u8; afi.octets_len()]; + let copy_len = bytes.len().min(out.len()); + out[..copy_len].copy_from_slice(&bytes[..copy_len]); + + if fill_remaining_ones { + if bit_len == 0 { + out.fill(0xFF); + return Ok(out); + } + + let last_bit = (bit_len - 1) as usize; + let last_byte = last_bit / 8; + let rem = (bit_len % 8) as u8; + + if rem != 0 && last_byte < out.len() { + // Set the (8-rem) trailing bits in the last byte to 1. + let mask: u8 = (1u8 << (8 - rem)) - 1; + out[last_byte] |= mask; + } + for b in out.iter_mut().skip(last_byte + 1) { + *b = 0xFF; + } + } + + Ok(out) +} + +fn parse_as_identifiers(ext_value: &[u8]) -> Result { + let (rem, obj) = parse_der(ext_value).map_err(|_| ())?; + if !rem.is_empty() { + return Err(()); + } + let seq = obj.as_sequence().map_err(|_| ())?; + let mut asnum: Option = None; + let mut rdi: Option = None; + for item in seq { + if item.class() != Class::ContextSpecific { + return Err(()); + } + match item.tag() { + Tag(0) => { + if asnum.is_some() { + return Err(()); + } + let inner = parse_explicit_inner(item)?; + asnum = Some(parse_as_identifier_choice(&inner)?); + } + Tag(1) => { + if rdi.is_some() { + return Err(()); + } + let inner = parse_explicit_inner(item)?; + rdi = Some(parse_as_identifier_choice(&inner)?); + } + _ => return Err(()), + } + } + Ok(AsResourceSet { asnum, rdi }) +} + +fn parse_explicit_inner<'a>(obj: &'a DerObject<'a>) -> Result, ()> { + let inner_der = obj.as_slice().map_err(|_| ())?; + let (rem, inner) = parse_der(inner_der).map_err(|_| ())?; + if !rem.is_empty() { + return Err(()); + } + Ok(inner) +} + +fn parse_as_identifier_choice(obj: &DerObject<'_>) -> Result { + match &obj.content { + BerObjectContent::Null => Ok(AsIdentifierChoice::Inherit), + BerObjectContent::Sequence(_) => { + let seq = obj.as_sequence().map_err(|_| ())?; + let mut items = Vec::with_capacity(seq.len()); + for item in seq { + items.push(parse_as_id_or_range(item)?); + } + Ok(AsIdentifierChoice::AsIdsOrRanges(items)) + } + _ => Err(()), + } +} + +fn parse_as_id_or_range(obj: &DerObject<'_>) -> Result { + match &obj.content { + BerObjectContent::Integer(_) => { + let v = obj.as_u64().map_err(|_| ())?; + if v > u32::MAX as u64 { + return Err(()); + } + Ok(AsIdOrRange::Id(v as u32)) + } + BerObjectContent::Sequence(_) => { + let seq = obj.as_sequence().map_err(|_| ())?; + if seq.len() != 2 { + return Err(()); + } + let min = seq[0].as_u64().map_err(|_| ())?; + let max = seq[1].as_u64().map_err(|_| ())?; + if min > u32::MAX as u64 || max > u32::MAX as u64 || min > max { + return Err(()); + } + Ok(AsIdOrRange::Range { + min: min as u32, + max: max as u32, + }) + } + _ => Err(()), + } +} + +fn canonicalize_prefix_addr(afi: Afi, prefix_len: u16, bytes: &[u8]) -> Vec { + let full_len = afi.octets_len(); + let mut addr = vec![0u8; full_len]; + let copy_len = bytes.len().min(full_len); + addr[..copy_len].copy_from_slice(&bytes[..copy_len]); + + if prefix_len == 0 { + return addr; + } + + let last_prefix_bit = (prefix_len - 1) as usize; + let last_prefix_byte = last_prefix_bit / 8; + let rem = (prefix_len % 8) as u8; + if rem != 0 && last_prefix_byte < addr.len() { + let mask: u8 = 0xFF << (8 - rem); + addr[last_prefix_byte] &= mask; + } + addr +} + +fn prefix_covers(resource: &IpPrefix, subject: &IpPrefix) -> bool { + if resource.afi != subject.afi { + return false; + } + if resource.prefix_len > subject.prefix_len { + return false; + } + let n = resource.prefix_len as usize; + let whole = n / 8; + let rem = (n % 8) as u8; + if resource.addr.len() != subject.addr.len() { + return false; + } + if resource.addr[..whole] != subject.addr[..whole] { + return false; + } + if rem == 0 { + return true; + } + let mask = 0xFFu8 << (8 - rem); + (resource.addr[whole] & mask) == (subject.addr[whole] & mask) +} + +fn prefix_range(afi: Afi, p: &IpPrefix) -> (u128, u128) { + let mut base_bytes = [0u8; 16]; + match afi { + Afi::Ipv4 => { + base_bytes[12..].copy_from_slice(&p.addr[..4]); + } + Afi::Ipv6 => { + base_bytes.copy_from_slice(&p.addr[..16]); + } + } + let base = u128::from_be_bytes(base_bytes); + let host_bits = (afi.ub() - p.prefix_len) as u32; + if host_bits == 0 { + return (base, base); + } + let mask = (1u128 << host_bits) - 1; + (base, base | mask) +} + +fn range_covers_prefix(afi: Afi, r: &IpAddressRange, p: &IpPrefix) -> bool { + let (p_min, p_max) = prefix_range(afi, p); + let r_min = bytes_to_u128(afi, &r.min); + let r_max = bytes_to_u128(afi, &r.max); + r_min <= p_min && p_max <= r_max +} + +fn bytes_to_u128(afi: Afi, bytes: &[u8]) -> u128 { + let mut out = [0u8; 16]; + match afi { + Afi::Ipv4 => { + let copy_len = bytes.len().min(4); + out[12..12 + copy_len].copy_from_slice(&bytes[..copy_len]); + } + Afi::Ipv6 => { + let copy_len = bytes.len().min(16); + out[..copy_len].copy_from_slice(&bytes[..copy_len]); + } + } + u128::from_be_bytes(out) +} diff --git a/src/model/rc/strict_name_tests.rs b/src/model/rc/strict_name_tests.rs new file mode 100644 index 0000000..22ab765 --- /dev/null +++ b/src/model/rc/strict_name_tests.rs @@ -0,0 +1,102 @@ +// Strict RPKI name profile tests. + +#[cfg(test)] +mod strict_name_tests { + use super::*; + + fn name_with_attrs(attrs: &[(&[u8], u8, &[u8])]) -> X509NameDer { + let mut rdns = Vec::new(); + for (oid, tag, value) in attrs { + let mut attr = Vec::new(); + attr.extend(der_tlv(0x06, oid)); + attr.extend(der_tlv(*tag, value)); + let attr = der_tlv(0x30, &attr); + let rdn = der_tlv(0x31, &attr); + rdns.extend(rdn); + } + X509NameDer(der_tlv(0x30, &rdns)) + } + + fn der_tlv(tag: u8, value: &[u8]) -> Vec { + let mut out = vec![tag]; + encode_len(value.len(), &mut out); + out.extend_from_slice(value); + out + } + + fn encode_len(len: usize, out: &mut Vec) { + if len < 0x80 { + out.push(len as u8); + return; + } + let mut bytes = Vec::new(); + let mut value = len; + while value > 0 { + bytes.push((value & 0xFF) as u8); + value >>= 8; + } + bytes.reverse(); + out.push(0x80 | bytes.len() as u8); + out.extend(bytes); + } + + #[test] + fn strict_name_accepts_printable_common_name_and_serial_number() { + let name = name_with_attrs(&[ + (&[0x55, 0x04, 0x03], 0x13, b"CN1"), + (&[0x55, 0x04, 0x05], 0x13, b"SN1"), + ]); + validate_strict_rpki_name(&name, "subject").expect("strict name"); + } + + #[test] + fn strict_name_rejects_utf8_common_name() { + let name = name_with_attrs(&[(&[0x55, 0x04, 0x03], 0x0C, b"CN1")]); + let err = validate_strict_rpki_name(&name, "subject").expect_err("strict name fails"); + assert!(err.to_string().contains("PrintableString"), "{err}"); + } + + #[test] + fn strict_name_rejects_duplicate_common_name() { + let name = name_with_attrs(&[ + (&[0x55, 0x04, 0x03], 0x13, b"CN1"), + (&[0x55, 0x04, 0x03], 0x13, b"CN2"), + ]); + let err = validate_strict_rpki_name(&name, "subject").expect_err("strict name fails"); + assert!(err.to_string().contains("exactly once"), "{err}"); + } + + #[test] + fn profile_rejects_rfc8360_v2_policy_oid() { + let extensions = RcExtensionsParsed { + basic_constraints: vec![BasicConstraintsProfile { + ca: true, + critical: true, + path_len_constraint: None, + }], + subject_key_identifier: Vec::new(), + authority_key_identifier: Vec::new(), + crl_distribution_points: Vec::new(), + authority_info_access: Vec::new(), + subject_info_access: Vec::new(), + certificate_policies: vec![( + vec![CertificatePoliciesProfile { + policy_oid: "1.3.6.1.5.5.7.14.3".to_string(), + qualifier_oids: Vec::new(), + }], + true, + )], + extension_oids: Vec::new(), + ip_resources: Vec::new(), + as_resources: Vec::new(), + }; + + let err = extensions + .validate_profile(true) + .expect_err("v2 policy OID must remain invalid"); + assert!( + matches!(&err, ResourceCertificateProfileError::InvalidCertificatePolicy(oid) if oid == "1.3.6.1.5.5.7.14.3"), + "{err}" + ); + } +} diff --git a/src/model/rc/types.rs b/src/model/rc/types.rs new file mode 100644 index 0000000..b1a24f6 --- /dev/null +++ b/src/model/rc/types.rs @@ -0,0 +1,564 @@ +// Resource certificate and resource-set model types. + +/// Resource Certificate kind (semantic classification). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ResourceCertKind { + Ca, + Ee, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ResourceCertificateRole { + TrustAnchor, + Ca, + SignedObjectEe, + RouterEe, +} + +/// A parsed RPKI Resource Certificate (RFC 6487) data model. +/// +/// This module intentionally focuses on the semantics needed by Signed Object validation and +/// object-specific EE certificate checks (MFT/ROA/ASPA), as described in +/// `rpki/specs/03_resource_certificate_rc.md`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ResourceCertificate { + pub raw_der: Vec, + pub tbs: RpkixTbsCertificate, + pub kind: ResourceCertKind, +} + +pub type ResourceCaCertificate = ResourceCertificate; +pub type ResourceEeCertificate = ResourceCertificate; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RpkixTbsCertificate { + pub version: u32, + pub serial_number: BigUint, + pub signature_algorithm: String, + pub issuer_name: X509NameDer, + pub subject_name: X509NameDer, + pub validity_not_before: UtcTime, + pub validity_not_after: UtcTime, + /// DER encoding of SubjectPublicKeyInfo. + pub subject_public_key_info: Vec, + pub extensions: RcExtensions, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RcExtensions { + pub basic_constraints_ca: bool, + pub basic_constraints: Option, + pub subject_key_identifier: Option>, + /// Authority Key Identifier (AKI) keyIdentifier value. + pub authority_key_identifier: Option>, + /// CRL Distribution Points URIs (fullName). + pub crl_distribution_points_uris: Option>, + /// Authority Information Access (AIA) caIssuers URIs. + pub ca_issuers_uris: Option>, + pub subject_info_access: Option, + pub certificate_policies_oid: Option, + pub certificate_policies: Option, + pub extension_oids: Vec, + + pub ip_resources: Option, + pub as_resources: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BasicConstraintsProfile { + pub ca: bool, + pub critical: bool, + pub path_len_constraint: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CertificatePoliciesProfile { + pub policy_oid: String, + pub qualifier_oids: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ResourceCertificateParsed { + pub raw_der: Vec, + pub version: X509Version, + pub serial_number: BigUint, + pub signature_algorithm: AlgorithmIdentifierValue, + pub tbs_signature_algorithm: AlgorithmIdentifierValue, + pub issuer_name: X509NameDer, + pub subject_name: X509NameDer, + pub validity_not_before: Asn1TimeUtc, + pub validity_not_after: Asn1TimeUtc, + /// DER encoding of SubjectPublicKeyInfo. + pub subject_public_key_info: Vec, + pub extensions: RcExtensionsParsed, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AlgorithmIdentifierValue { + pub oid: String, + pub parameters: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AlgorithmParametersValue { + pub class: Asn1Class, + pub tag: Asn1Tag, + pub data: Vec, +} + +impl AlgorithmIdentifierValue { + pub fn params_absent_or_null(&self) -> bool { + match &self.parameters { + None => true, + Some(p) if p.class == Asn1Class::Universal && p.tag == Asn1Tag::Null => true, + Some(_p) => false, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RcExtensionsParsed { + pub basic_constraints: Vec, + pub subject_key_identifier: Vec<(Vec, bool)>, + pub authority_key_identifier: Vec<(AuthorityKeyIdentifierParsed, bool)>, + pub crl_distribution_points: Vec<(CrlDistributionPointsParsed, bool)>, + pub authority_info_access: Vec<(AuthorityInfoAccessParsed, bool)>, + pub subject_info_access: Vec<(SubjectInfoAccessParsed, bool)>, + pub certificate_policies: Vec<(Vec, bool)>, + pub extension_oids: Vec, + pub ip_resources: Vec<(IpResourceSet, bool)>, + pub as_resources: Vec<(AsResourceSet, bool)>, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AuthorityKeyIdentifierParsed { + pub key_identifier: Option>, + pub has_authority_cert_issuer: bool, + pub has_authority_cert_serial: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AuthorityInfoAccessParsed { + pub ca_issuers_uris: Vec, + pub ca_issuers_access_location_not_uri: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CrlDistributionPointsParsed { + pub distribution_points: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CrlDistributionPointParsed { + pub distribution_point_present: bool, + pub reasons_present: bool, + pub crl_issuer_present: bool, + pub name_relative_to_crl_issuer_present: bool, + pub full_name_uris: Vec, + pub full_name_not_uri: bool, + pub full_name_present: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SubjectInfoAccessParsed { + pub access_descriptions: Vec, + pub signed_object_uris: Vec, + pub signed_object_access_location_not_uri: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum SubjectInfoAccess { + Ca(SubjectInfoAccessCa), + Ee(SubjectInfoAccessEe), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SubjectInfoAccessCa { + pub access_descriptions: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SubjectInfoAccessEe { + pub signed_object_uris: Vec, + /// The full list of access descriptions as carried in the SIA extension. + pub access_descriptions: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AccessDescription { + pub access_method_oid: String, + pub access_location: String, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +pub enum Afi { + Ipv4, + Ipv6, +} + +impl Afi { + pub fn ub(self) -> u16 { + match self { + Afi::Ipv4 => 32, + Afi::Ipv6 => 128, + } + } + + pub fn octets_len(self) -> usize { + match self { + Afi::Ipv4 => 4, + Afi::Ipv6 => 16, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct IpResourceSet { + pub families: Vec, +} + +impl IpResourceSet { + /// Decode the DER bytes carried inside the X.509 `extnValue` OCTET STRING for + /// `id-pe-ipAddrBlocks` (RFC 3779 / RFC 6487). + pub fn decode_extn_value(extn_value: &[u8]) -> Result { + parse_ip_addr_blocks(extn_value).map_err(|_| IpResourceSetDecodeError::InvalidEncoding) + } + + pub fn is_all_inherit(&self) -> bool { + self.families + .iter() + .all(|f| matches!(f.choice, IpAddressChoice::Inherit)) + } + + pub fn has_any_inherit(&self) -> bool { + self.families + .iter() + .any(|f| matches!(f.choice, IpAddressChoice::Inherit)) + } + + pub fn contains_prefix(&self, prefix: &IpPrefix) -> bool { + self.families.iter().any(|fam| fam.contains_prefix(prefix)) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum IpResourceSetDecodeError { + #[error("invalid ipAddrBlocks encoding (RFC 3779 §2.2.3; RFC 6487 §4.8.10)")] + InvalidEncoding, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct IpAddressFamily { + pub afi: Afi, + pub choice: IpAddressChoice, +} + +impl IpAddressFamily { + pub fn contains_prefix(&self, prefix: &IpPrefix) -> bool { + if self.afi != prefix.afi { + return false; + } + match &self.choice { + IpAddressChoice::Inherit => true, + IpAddressChoice::AddressesOrRanges(items) => items.iter().any(|item| match item { + IpAddressOrRange::Prefix(p) => prefix_covers(p, prefix), + IpAddressOrRange::Range(r) => range_covers_prefix(self.afi, r, prefix), + }), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum IpAddressChoice { + Inherit, + AddressesOrRanges(Vec), +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum IpAddressOrRange { + Prefix(IpPrefix), + Range(IpAddressRange), +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct IpAddressRange { + pub min: Vec, + pub max: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +pub struct IpPrefix { + pub afi: Afi, + pub prefix_len: u16, + pub addr: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct AsResourceSet { + pub asnum: Option, + pub rdi: Option, +} + +impl AsResourceSet { + /// Decode the DER bytes carried inside the X.509 `extnValue` OCTET STRING for + /// `id-pe-autonomousSysIds` (RFC 3779 / RFC 6487). + pub fn decode_extn_value(extn_value: &[u8]) -> Result { + parse_as_identifiers(extn_value).map_err(|_| AsResourceSetDecodeError::InvalidEncoding) + } + + pub fn is_asnum_inherit(&self) -> bool { + matches!(self.asnum, Some(AsIdentifierChoice::Inherit)) + } + + pub fn has_any_range(&self) -> bool { + self.asnum.as_ref().map(|c| c.has_range()).unwrap_or(false) + || self.rdi.as_ref().map(|c| c.has_range()).unwrap_or(false) + } + + pub fn asnum_single_id(&self) -> Option { + match self.asnum.as_ref()? { + AsIdentifierChoice::Inherit => None, + AsIdentifierChoice::AsIdsOrRanges(items) => { + if items.len() != 1 { + return None; + } + match &items[0] { + AsIdOrRange::Id(v) => Some(*v), + AsIdOrRange::Range { .. } => None, + } + } + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum AsResourceSetDecodeError { + #[error("invalid autonomousSysIds encoding (RFC 3779 §3.2.3; RFC 6487 §4.8.11)")] + InvalidEncoding, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum AsIdentifierChoice { + Inherit, + AsIdsOrRanges(Vec), +} + +impl AsIdentifierChoice { + pub fn has_range(&self) -> bool { + match self { + AsIdentifierChoice::Inherit => false, + AsIdentifierChoice::AsIdsOrRanges(items) => { + items.iter().any(|i| matches!(i, AsIdOrRange::Range { .. })) + } + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum AsIdOrRange { + Id(u32), + Range { min: u32, max: u32 }, +} + +#[derive(Debug, thiserror::Error)] +pub enum ResourceCertificateParseError { + #[error("X.509 parse error: {0} (RFC 5280 §4.1; RFC 6487 §4)")] + Parse(String), + + #[error("trailing bytes after certificate DER: {0} bytes (DER; RFC 5280 §4.1)")] + TrailingBytes(usize), + + #[error("invalid RFC 3779 IP resources extension encoding (RFC 6487 §4.8.10; RFC 3779 §2.2)")] + InvalidIpResourcesEncoding, + + #[error("invalid RFC 3779 AS resources extension encoding (RFC 6487 §4.8.11; RFC 3779 §3.2)")] + InvalidAsResourcesEncoding, +} + +#[derive(Debug, thiserror::Error)] +pub enum ResourceCertificateProfileError { + #[error("{0}")] + InvalidTimeEncoding(#[from] InvalidTimeEncodingError), + + #[error("certificate version must be v3 (RFC 5280 §4.1; RFC 6487 §4)")] + InvalidVersion, + + #[error("signatureAlgorithm does not match tbsCertificate.signature (RFC 5280 §4.1)")] + SignatureAlgorithmMismatch, + + #[error( + "unsupported signature algorithm (expected sha256WithRSAEncryption {OID_SHA256_WITH_RSA_ENCRYPTION}) (RFC 7935 §2; RFC 6487 §4)" + )] + UnsupportedSignatureAlgorithm, + + #[error("invalid signature algorithm parameters (RFC 5280 §4.1.1.2)")] + InvalidSignatureAlgorithmParameters, + + #[error( + "{role} Name strict validation failed: {detail} (RFC 6487 §4.4; RFC 5280 §4.1.2.4/§4.1.2.6)" + )] + StrictName { role: &'static str, detail: String }, + + #[error("duplicate extension: {0} (RFC 5280 §4.2; RFC 6487 §4.8)")] + DuplicateExtension(&'static str), + + #[error("SubjectKeyIdentifier criticality must be non-critical (RFC 6487 §4.8.2)")] + SkiCriticality, + + #[error("SubjectInfoAccess criticality must be non-critical (RFC 6487 §4.8.8)")] + SiaCriticality, + + #[error("certificatePolicies criticality must be critical (RFC 6487 §4.8.9)")] + CertificatePoliciesCriticality, + + #[error("certificatePolicies must be present (RFC 6487 §4.8.9)")] + CertificatePoliciesMissing, + + #[error( + "certificatePolicies must contain RPKI policy OID {OID_CP_IPADDR_ASNUMBER}, got {0} (RFC 6487 §4.8.9)" + )] + InvalidCertificatePolicy(String), + + #[error("certificatePolicies may contain at most one CPS qualifier (RFC 6487 §4.8.9)")] + CertificatePoliciesTooManyQualifiers, + + #[error( + "certificatePolicies qualifier must be id-qt-cps ({OID_QT_CPS}), got {0} (RFC 6487 §4.8.9)" + )] + CertificatePoliciesInvalidQualifier(String), + + #[error("basicConstraints must be present in CA certificates (RFC 6487 §4.8.1)")] + BasicConstraintsMissing, + + #[error("basicConstraints criticality must be critical in CA certificates (RFC 6487 §4.8.1)")] + BasicConstraintsCriticality, + + #[error("basicConstraints cA must be TRUE in CA certificates (RFC 6487 §4.8.1)")] + BasicConstraintsCaFalse, + + #[error( + "basicConstraints pathLenConstraint must be absent in CA certificates (RFC 6487 §4.8.1)" + )] + BasicConstraintsPathLenPresent, + + #[error("basicConstraints must be absent in EE certificates (RFC 6487 §4.8.1)")] + BasicConstraintsEeMustOmit, + + #[error("extension {oid} is not permitted for {role} resource certificates (RFC 6487 §4.8)")] + DisallowedExtension { role: &'static str, oid: String }, + + #[error("autonomousSysIds RDI field must be absent (RFC 6487 §4.8.11; RFC 3779 §3.2.3)")] + AsResourcesRdiPresent, + + #[error( + "SIA id-ad-signedObject accessLocation must be URI (RFC 6487 §4.8.8.2; RFC 5280 §4.2.2.2)" + )] + SignedObjectSiaNotUri, + + #[error("SIA id-ad-signedObject must include at least one rsync:// URI (RFC 6487 §4.8.8.2)")] + SignedObjectSiaNoRsync, + + #[error("ipAddrBlocks criticality must be critical when present (RFC 6487 §4.8.10)")] + IpResourcesCriticality, + + #[error("autonomousSysIds criticality must be critical when present (RFC 6487 §4.8.11)")] + AsResourcesCriticality, + + #[error( + "authorityKeyIdentifier must be present in non-self-signed certificates (RFC 6487 §4.8.3; RFC 5280 §4.2.1.1)" + )] + AkiMissing, + + #[error( + "authorityKeyIdentifier criticality must be non-critical (RFC 6487 §4.8.3; RFC 5280 §4.2.1.1)" + )] + AkiCriticality, + + #[error( + "authorityKeyIdentifier authorityCertIssuer MUST NOT be present (RFC 6487 §4.8.3; RFC 5280 §4.2.1.1)" + )] + AkiAuthorityCertIssuerPresent, + + #[error( + "authorityKeyIdentifier authorityCertSerialNumber MUST NOT be present (RFC 6487 §4.8.3; RFC 5280 §4.2.1.1)" + )] + AkiAuthorityCertSerialPresent, + + #[error( + "self-signed certificate authorityKeyIdentifier must equal subjectKeyIdentifier when present (RFC 6487 §4.8.3)" + )] + AkiSelfSignedNotEqualSki, + + #[error( + "CRLDistributionPoints must be present in non-self-signed certificates (RFC 6487 §4.8.6; RFC 5280 §4.2.1.13)" + )] + CrlDistributionPointsMissing, + + #[error( + "CRLDistributionPoints criticality must be non-critical (RFC 6487 §4.8.6; RFC 5280 §4.2.1.13)" + )] + CrlDistributionPointsCriticality, + + #[error("CRLDistributionPoints MUST be omitted in self-signed certificates (RFC 6487 §4.8.6)")] + CrlDistributionPointsSelfSignedMustOmit, + + #[error("CRLDistributionPoints must contain exactly one DistributionPoint (RFC 6487 §4.8.6)")] + CrlDistributionPointsNotSingle, + + #[error("CRLDistributionPoints distributionPoint field MUST be present (RFC 6487 §4.8.6)")] + CrlDistributionPointsNoDistributionPoint, + + #[error("CRLDistributionPoints reasons field MUST be omitted (RFC 6487 §4.8.6)")] + CrlDistributionPointsHasReasons, + + #[error("CRLDistributionPoints cRLIssuer field MUST be omitted (RFC 6487 §4.8.6)")] + CrlDistributionPointsHasCrlIssuer, + + #[error( + "CRLDistributionPoints distributionPoint MUST contain fullName and MUST NOT contain nameRelativeToCRLIssuer (RFC 6487 §4.8.6)" + )] + CrlDistributionPointsInvalidName, + + #[error( + "CRLDistributionPoints fullName must contain only URI GeneralNames (RFC 6487 §4.8.6; RFC 5280 §4.2.1.6)" + )] + CrlDistributionPointsFullNameNotUri, + + #[error("CRLDistributionPoints must include at least one rsync:// URI (RFC 6487 §4.8.6)")] + CrlDistributionPointsNoRsync, + + #[error( + "authorityInfoAccess must be present in non-self-signed certificates (RFC 6487 §4.8.7; RFC 5280 §4.2.2.1)" + )] + AuthorityInfoAccessMissing, + + #[error( + "authorityInfoAccess criticality must be non-critical (RFC 6487 §4.8.7; RFC 5280 §4.2.2.1)" + )] + AuthorityInfoAccessCriticality, + + #[error("authorityInfoAccess MUST be omitted in self-signed certificates (RFC 6487 §4.8.7)")] + AuthorityInfoAccessSelfSignedMustOmit, + + #[error( + "authorityInfoAccess id-ad-caIssuers accessLocation must be URI (RFC 6487 §4.8.7; RFC 5280 §4.2.2.1)" + )] + AuthorityInfoAccessCaIssuersNotUri, + + #[error("authorityInfoAccess must include at least one id-ad-caIssuers URI (RFC 6487 §4.8.7)")] + AuthorityInfoAccessMissingCaIssuers, + + #[error("authorityInfoAccess must include at least one rsync:// URI (RFC 6487 §4.8.7)")] + AuthorityInfoAccessNoRsync, +} + +#[derive(Debug, thiserror::Error)] +pub enum ResourceCertificateDecodeError { + #[error("{0}")] + Parse(#[from] ResourceCertificateParseError), + + #[error("{0}")] + Validate(#[from] ResourceCertificateProfileError), +} + +pub type ResourceCertificateError = ResourceCertificateDecodeError; diff --git a/src/model/roa.rs b/src/model/roa.rs new file mode 100644 index 0000000..2dcdd51 --- /dev/null +++ b/src/model/roa.rs @@ -0,0 +1,631 @@ +use crate::model::common::{DerReader, der_take_tlv}; +use crate::model::oid::OID_CT_ROUTE_ORIGIN_AUTHZ; +use crate::model::rc::{Afi as RcAfi, IpPrefix as RcIpPrefix, ResourceCertificate}; +use crate::model::signed_object::{ + RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectDecodeError, SignedObjectParseError, + SignedObjectValidateError, +}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RoaObject { + pub signed_object: RpkiSignedObject, + pub econtent_type: String, + pub roa: RoaEContent, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RoaObjectParsed { + pub signed_object: RpkiSignedObjectParsed, + pub econtent_type: String, + pub roa: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RoaEContent { + pub version: u32, + pub as_id: u32, + pub ip_addr_blocks: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RoaEContentParsed { + der: Vec, +} + +#[derive(Debug, thiserror::Error)] +pub enum RoaParseError { + #[error("signed object parse error: {0} (RFC 6488 §2-§3; RFC 9589 §4)")] + SignedObject(#[from] SignedObjectParseError), + #[error("ROA parse error: {0} (RFC 9582 §4; DER)")] + Parse(String), + + #[error("ROA trailing bytes: {0} bytes (RFC 9582 §4; DER)")] + TrailingBytes(usize), +} + +#[derive(Debug, thiserror::Error)] +pub enum RoaProfileError { + #[error("signed object profile error: {0} (RFC 6488 §2-§3; RFC 9589 §4)")] + SignedObject(#[from] SignedObjectValidateError), + + #[error("ROA eContentType must be {OID_CT_ROUTE_ORIGIN_AUTHZ}, got {0} (RFC 9582 §3)")] + InvalidEContentType(String), + + #[error("ROA profile decode error: {0} (RFC 9582 §4; DER)")] + ProfileDecode(String), + + #[error("RouteOriginAttestation must be a SEQUENCE of 2 or 3 elements, got {0} (RFC 9582 §4)")] + InvalidAttestationSequenceLen(usize), + + #[error("ROA version must be 0, got {0} (RFC 9582 §4.1)")] + InvalidVersion(u64), + + #[error("ROA asID out of range (0..=4294967295), got {0} (RFC 9582 §4.2)")] + AsIdOutOfRange(u64), + + #[error("ROA ipAddrBlocks must have length 1..2, got {0} (RFC 9582 §4; RFC 9582 §4.3.1)")] + InvalidIpAddrBlocksLen(usize), + + #[error("ROAIPAddressFamily must be a SEQUENCE of 2 elements (RFC 9582 §4.3.1)")] + InvalidIpAddressFamily, + + #[error("ROA addressFamily must be an OCTET STRING of 2 bytes (RFC 9582 §4.3.1)")] + InvalidAddressFamily, + + #[error("ROA addressFamily AFI not supported: {0:02X?} (RFC 9582 §4.3.1)")] + UnsupportedAfi(Vec), + + #[error("ROA contains duplicate AFI {0:?} (RFC 9582 §4.3.1)")] + DuplicateAfi(RoaAfi), + + #[error("ROAAddresses must have at least one entry (RFC 9582 §4.3.2)")] + EmptyAddressList, + + #[error("ROAIPAddress must be a SEQUENCE of 1..2 elements (RFC 9582 §4.3.2)")] + InvalidRoaIpAddress, + + #[error("ROAIPAddress.address must be a BIT STRING (RFC 9582 §4.3.2.1; RFC 3779 §2.2.3.8)")] + InvalidPrefixBitString, + + #[error( + "ROAIPAddress.address has invalid unused bits encoding (RFC 9582 §4.3.2.1; RFC 3779 §2.2.3.8)" + )] + InvalidPrefixUnusedBits, + + #[error( + "ROAIPAddress.address prefix length {prefix_len} out of range for {afi:?} (RFC 9582 §4.3.2.1)" + )] + PrefixLenOutOfRange { afi: RoaAfi, prefix_len: u16 }, + + #[error( + "ROAIPAddress.maxLength out of range for {afi:?}: prefix_len={prefix_len}, max_len={max_len} (RFC 9582 §4.3.2.2)" + )] + InvalidMaxLength { + afi: RoaAfi, + prefix_len: u16, + max_len: u16, + }, +} + +impl From for RoaProfileError { + fn from(value: SignedObjectDecodeError) -> Self { + match value { + SignedObjectDecodeError::Parse(e) => RoaProfileError::ProfileDecode(e.to_string()), + SignedObjectDecodeError::Validate(e) => RoaProfileError::SignedObject(e), + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum RoaDecodeError { + #[error("{0}")] + Parse(#[from] RoaParseError), + + #[error("{0}")] + Validate(#[from] RoaProfileError), +} + +#[derive(Debug, thiserror::Error)] +pub enum RoaValidateError { + #[error("ROA EE certificate must not contain AS resources extension (RFC 9582 §5)")] + EeAsResourcesPresent, + + #[error("ROA EE certificate must contain IP resources extension (RFC 9582 §5)")] + EeIpResourcesMissing, + + #[error("ROA EE certificate IP resources must not use inherit (RFC 9582 §5)")] + EeIpResourcesInherit, + + #[error( + "ROA prefix not covered by EE certificate IP resources: {afi:?} {addr:?}/{prefix_len} (RFC 9582 §5; RFC 3779 §2.3)" + )] + PrefixNotInEeResources { + afi: RoaAfi, + addr: Vec, + prefix_len: u16, + }, +} + +impl RoaObject { + /// Parse step of scheme A (`parse → validate → verify`). + pub fn parse_der(der: &[u8]) -> Result { + let signed_object = RpkiSignedObject::parse_der(der)?; + let econtent_type = signed_object + .signed_data + .encap_content_info + .econtent_type + .clone(); + let roa = signed_object + .signed_data + .encap_content_info + .econtent + .as_deref() + .map(RoaEContent::parse_der) + .transpose()?; + Ok(RoaObjectParsed { + signed_object, + econtent_type, + roa, + }) + } + + /// Profile validate step of scheme A (`parse → validate → verify`). + /// + /// `RoaObject` is already profile-validated when constructed via `decode_der()` / + /// `RoaObjectParsed::validate_profile()`. + pub fn validate_profile(&self) -> Result<(), RoaProfileError> { + Ok(()) + } + + pub fn decode_der(der: &[u8]) -> Result { + Ok(Self::parse_der(der)?.validate_profile()?) + } + + pub fn decode_der_with_strict_options( + der: &[u8], + strict_cms_der: bool, + strict_name: bool, + ) -> Result { + let signed_object = + RpkiSignedObject::decode_der_with_strict_options(der, strict_cms_der, strict_name) + .map_err(RoaProfileError::from)?; + Self::from_signed_object(signed_object) + } + + pub fn from_signed_object(signed_object: RpkiSignedObject) -> Result { + let econtent_type = signed_object + .signed_data + .encap_content_info + .econtent_type + .clone(); + if econtent_type != OID_CT_ROUTE_ORIGIN_AUTHZ { + return Err(RoaProfileError::InvalidEContentType(econtent_type).into()); + } + + let roa = RoaEContent::decode_der(&signed_object.signed_data.encap_content_info.econtent)?; + Ok(Self { + roa, + signed_object, + econtent_type: OID_CT_ROUTE_ORIGIN_AUTHZ.to_string(), + }) + } + + /// Validate this ROA's embedded EE certificate resources. + pub fn validate_embedded_ee_cert(&self) -> Result<(), RoaValidateError> { + let ee = &self.signed_object.signed_data.certificates[0].resource_cert; + self.roa.validate_against_ee_cert(ee) + } +} + +#[derive( + Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize, +)] +pub enum RoaAfi { + Ipv4, + Ipv6, +} + +impl RoaAfi { + fn ub(self) -> u16 { + match self { + RoaAfi::Ipv4 => 32, + RoaAfi::Ipv6 => 128, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RoaIpAddressFamily { + pub afi: RoaAfi, + pub addresses: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct RoaIpAddress { + pub prefix: IpPrefix, + pub max_length: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct IpPrefix { + pub afi: RoaAfi, + /// Prefix length in bits. + pub prefix_len: u16, + /// Network order address bytes (always 16 bytes), with host bits cleared. + /// + /// For IPv4 prefixes, only the first 4 bytes are used and the remaining 12 bytes are zero. + pub addr: [u8; 16], +} + +impl IpPrefix { + pub fn addr_bytes(&self) -> &[u8] { + match self.afi { + RoaAfi::Ipv4 => &self.addr[..4], + RoaAfi::Ipv6 => &self.addr[..16], + } + } +} + +impl RoaEContent { + /// Parse step of scheme A (`parse → validate → verify`). + pub fn parse_der(der: &[u8]) -> Result { + let (_tag, _value, rem) = der_take_tlv(der).map_err(RoaParseError::Parse)?; + if !rem.is_empty() { + return Err(RoaParseError::TrailingBytes(rem.len())); + } + Ok(RoaEContentParsed { der: der.to_vec() }) + } + + /// Profile validate step of scheme A (`parse → validate → verify`). + /// + /// `RoaEContent` is already profile-validated when constructed via `decode_der()` / + /// `RoaEContentParsed::validate_profile()`. + pub fn validate_profile(&self) -> Result<(), RoaProfileError> { + Ok(()) + } + + /// Decode the DER-encoded RouteOriginAttestation defined in RFC 9582 §4 (`parse + validate`). + pub fn decode_der(der: &[u8]) -> Result { + Ok(Self::parse_der(der)?.validate_profile()?) + } + + pub fn canonicalize(&mut self) { + self.ip_addr_blocks.sort_by_key(|f| f.afi); + for fam in &mut self.ip_addr_blocks { + fam.addresses.sort(); + fam.addresses.dedup(); + } + } + + /// Validate ROA payload against the embedded EE resource certificate (RFC 9582 §5). + /// + /// This performs the EE/payload semantic checks that do not require certificate path + /// validation. + pub fn validate_against_ee_cert( + &self, + ee: &ResourceCertificate, + ) -> Result<(), RoaValidateError> { + if ee.tbs.extensions.as_resources.is_some() { + return Err(RoaValidateError::EeAsResourcesPresent); + } + + let ip = ee + .tbs + .extensions + .ip_resources + .as_ref() + .ok_or(RoaValidateError::EeIpResourcesMissing)?; + + if ip.has_any_inherit() { + return Err(RoaValidateError::EeIpResourcesInherit); + } + + for fam in &self.ip_addr_blocks { + for entry in &fam.addresses { + let rc_prefix = roa_prefix_to_rc(&entry.prefix); + if !ip.contains_prefix(&rc_prefix) { + return Err(RoaValidateError::PrefixNotInEeResources { + afi: entry.prefix.afi, + addr: entry.prefix.addr_bytes().to_vec(), + prefix_len: entry.prefix.prefix_len, + }); + } + } + } + Ok(()) + } +} + +impl RoaObjectParsed { + pub fn validate_profile(self) -> Result { + let signed_object = self.signed_object.validate_profile()?; + let econtent_type = signed_object + .signed_data + .encap_content_info + .econtent_type + .clone(); + if econtent_type != OID_CT_ROUTE_ORIGIN_AUTHZ { + return Err(RoaProfileError::InvalidEContentType(econtent_type)); + } + let roa = self + .roa + .ok_or_else(|| RoaProfileError::ProfileDecode("ROA.eContent missing".into()))? + .validate_profile()?; + Ok(RoaObject { + signed_object, + econtent_type: OID_CT_ROUTE_ORIGIN_AUTHZ.to_string(), + roa, + }) + } +} + +impl RoaEContentParsed { + pub fn validate_profile(self) -> Result { + fn count_elements(mut r: DerReader<'_>) -> Result { + let mut n = 0usize; + while !r.is_empty() { + r.skip_any()?; + n += 1; + } + Ok(n) + } + + let mut r = DerReader::new(&self.der); + let mut seq = r.take_sequence().map_err(RoaProfileError::ProfileDecode)?; + if !r.is_empty() { + return Err(RoaProfileError::ProfileDecode( + "trailing bytes after RouteOriginAttestation".into(), + )); + } + + let elem_count = + count_elements(seq).map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?; + if elem_count != 2 && elem_count != 3 { + return Err(RoaProfileError::InvalidAttestationSequenceLen(elem_count)); + } + + let mut version: u32 = 0; + if elem_count == 3 { + if seq + .peek_tag() + .map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))? + != 0xA0 + { + return Err(RoaProfileError::ProfileDecode( + "RouteOriginAttestation.version must be [0] EXPLICIT INTEGER".into(), + )); + } + let (inner_tag, inner_val) = seq + .take_explicit(0xA0) + .map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?; + if inner_tag != 0x02 { + return Err(RoaProfileError::ProfileDecode( + "RouteOriginAttestation.version must be [0] EXPLICIT INTEGER".into(), + )); + } + let v = crate::model::common::der_uint_from_bytes(inner_val) + .map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?; + if v != 0 { + return Err(RoaProfileError::InvalidVersion(v)); + } + version = 0; + } + + let as_id_u64 = seq + .take_uint_u64() + .map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?; + if as_id_u64 > u32::MAX as u64 { + return Err(RoaProfileError::AsIdOutOfRange(as_id_u64)); + } + let as_id = as_id_u64 as u32; + let ip_addr_blocks = parse_ip_addr_blocks_cursor( + seq.take_sequence() + .map_err(|e| RoaProfileError::ProfileDecode(format!("ipAddrBlocks: {e}")))?, + )?; + + if !seq.is_empty() { + // Extra elements beyond the expected 2..3. + let extra = + count_elements(seq).map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?; + return Err(RoaProfileError::InvalidAttestationSequenceLen( + elem_count + extra, + )); + } + + let mut out = RoaEContent { + version, + as_id, + ip_addr_blocks, + }; + out.canonicalize(); + Ok(out) + } +} + +fn roa_prefix_to_rc(p: &IpPrefix) -> RcIpPrefix { + let afi = match p.afi { + RoaAfi::Ipv4 => RcAfi::Ipv4, + RoaAfi::Ipv6 => RcAfi::Ipv6, + }; + RcIpPrefix { + afi, + prefix_len: p.prefix_len, + addr: p.addr_bytes().to_vec(), + } +} + +fn parse_ip_addr_blocks_cursor( + mut seq: DerReader<'_>, +) -> Result, RoaProfileError> { + fn count_elements(mut r: DerReader<'_>) -> Result { + let mut n = 0usize; + while !r.is_empty() { + r.skip_any()?; + n += 1; + } + Ok(n) + } + + let fam_count = + count_elements(seq).map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?; + if fam_count == 0 || fam_count > 2 { + return Err(RoaProfileError::InvalidIpAddrBlocksLen(fam_count)); + } + + let mut out: Vec = Vec::with_capacity(fam_count); + while !seq.is_empty() { + let family = parse_ip_address_family_cursor( + seq.take_sequence() + .map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?, + )?; + if out.iter().any(|f| f.afi == family.afi) { + return Err(RoaProfileError::DuplicateAfi(family.afi)); + } + out.push(family); + } + Ok(out) +} + +fn parse_ip_address_family_cursor( + mut fam: DerReader<'_>, +) -> Result { + let afi = { + let bytes = fam + .take_octet_string() + .map_err(|_e| RoaProfileError::InvalidAddressFamily)?; + if bytes.len() != 2 { + return Err(RoaProfileError::InvalidAddressFamily); + } + match bytes { + [0x00, 0x01] => RoaAfi::Ipv4, + [0x00, 0x02] => RoaAfi::Ipv6, + _ => return Err(RoaProfileError::UnsupportedAfi(bytes.to_vec())), + } + }; + + let mut addrs = fam + .take_sequence() + .map_err(|_e| RoaProfileError::InvalidIpAddressFamily)?; + if !fam.is_empty() { + return Err(RoaProfileError::InvalidIpAddressFamily); + } + + if addrs.is_empty() { + return Err(RoaProfileError::EmptyAddressList); + } + let mut addresses: Vec = Vec::new(); + while !addrs.is_empty() { + let entry = addrs + .take_sequence() + .map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?; + addresses.push(parse_roa_ip_address_cursor(afi, entry)?); + } + + Ok(RoaIpAddressFamily { afi, addresses }) +} + +fn parse_roa_ip_address_cursor( + afi: RoaAfi, + mut seq: DerReader<'_>, +) -> Result { + if seq.is_empty() { + return Err(RoaProfileError::InvalidRoaIpAddress); + } + + let (unused_bits, bytes) = seq + .take_bit_string() + .map_err(|_e| RoaProfileError::InvalidPrefixBitString)?; + let prefix = parse_prefix_bits_bytes(afi, unused_bits, bytes)?; + + let max_length = if !seq.is_empty() { + let v = seq + .take_uint_u64() + .map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?; + let max_len: u16 = v + .try_into() + .map_err(|_e| RoaProfileError::InvalidMaxLength { + afi, + prefix_len: prefix.prefix_len, + max_len: u16::MAX, + })?; + Some(max_len) + } else { + None + }; + + if !seq.is_empty() { + return Err(RoaProfileError::InvalidRoaIpAddress); + } + + if let Some(max_len) = max_length { + let ub = afi.ub(); + if max_len > ub || max_len < prefix.prefix_len { + return Err(RoaProfileError::InvalidMaxLength { + afi, + prefix_len: prefix.prefix_len, + max_len, + }); + } + } + + Ok(RoaIpAddress { prefix, max_length }) +} + +fn parse_prefix_bits_bytes( + afi: RoaAfi, + unused_bits: u8, + bytes: &[u8], +) -> Result { + if unused_bits > 7 { + return Err(RoaProfileError::InvalidPrefixUnusedBits); + } + if bytes.is_empty() { + if unused_bits != 0 { + return Err(RoaProfileError::InvalidPrefixUnusedBits); + } + } else if unused_bits != 0 { + let mask = (1u8 << unused_bits) - 1; + if (bytes[bytes.len() - 1] & mask) != 0 { + return Err(RoaProfileError::InvalidPrefixUnusedBits); + } + } + + let prefix_len = (bytes.len() * 8) + .checked_sub(unused_bits as usize) + .ok_or(RoaProfileError::InvalidPrefixUnusedBits)? as u16; + if prefix_len > afi.ub() { + return Err(RoaProfileError::PrefixLenOutOfRange { afi, prefix_len }); + } + + let addr = canonicalize_prefix_addr(afi, prefix_len, bytes); + Ok(IpPrefix { + afi, + prefix_len, + addr, + }) +} + +fn canonicalize_prefix_addr(afi: RoaAfi, prefix_len: u16, bytes: &[u8]) -> [u8; 16] { + let full_len = match afi { + RoaAfi::Ipv4 => 4, + RoaAfi::Ipv6 => 16, + }; + let mut addr = [0u8; 16]; + let copy_len = bytes.len().min(full_len); + addr[..copy_len].copy_from_slice(&bytes[..copy_len]); + + if prefix_len == 0 { + return addr; + } + + let last_prefix_bit = (prefix_len - 1) as usize; + let last_prefix_byte = last_prefix_bit / 8; + let rem = (prefix_len % 8) as u8; + if rem != 0 { + let mask: u8 = 0xFF << (8 - rem); + if last_prefix_byte < full_len { + addr[last_prefix_byte] &= mask; + } + } + addr +} diff --git a/src/model/router_cert.rs b/src/model/router_cert.rs new file mode 100644 index 0000000..bcfa11b --- /dev/null +++ b/src/model/router_cert.rs @@ -0,0 +1,365 @@ +#![allow(clippy::too_many_arguments)] + +use crate::model::oid::{ + OID_EC_PUBLIC_KEY, OID_EXTENDED_KEY_USAGE_RAW, OID_KP_BGPSEC_ROUTER, OID_SECP256R1, +}; +use crate::model::rc::{ + AsIdOrRange, AsIdentifierChoice, ResourceCertKind, ResourceCertificate, + ResourceCertificateParseError, ResourceCertificateParsed, ResourceCertificateProfileError, + ResourceCertificateRole, +}; +use crate::validation::cert_path::{CertPathError, validate_ee_cert_path_with_predecoded_ee}; +use x509_parser::extensions::ParsedExtension; +use x509_parser::prelude::{FromDer, X509Certificate}; +use x509_parser::public_key::PublicKey; +use x509_parser::x509::SubjectPublicKeyInfo; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BgpsecRouterCertificateParsed { + pub rc_parsed: ResourceCertificateParsed, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BgpsecRouterCertificate { + pub raw_der: Vec, + pub resource_cert: ResourceCertificate, + pub subject_key_identifier: Vec, + pub spki_der: Vec, + pub asns: Vec, +} + +#[derive(Debug, thiserror::Error)] +pub enum BgpsecRouterCertificateParseError { + #[error("resource certificate parse error: {0} (RFC 5280 §4.1; RFC 6487 §4; RFC 8209 §3.1)")] + ResourceCertificate(#[from] ResourceCertificateParseError), + + #[error("X.509 parse error: {0} (RFC 5280 §4.1; RFC 8209 §3.1)")] + X509(String), + + #[error("trailing bytes after router certificate DER: {0} bytes (DER; RFC 5280 §4.1)")] + TrailingBytes(usize), + + #[error("router SubjectPublicKeyInfo parse error: {0} (RFC 5280 §4.1.2.7; RFC 8208 §3.1)")] + SpkiParse(String), + + #[error("trailing bytes after router SubjectPublicKeyInfo DER: {0} bytes (DER; RFC 8208 §3.1)")] + SpkiTrailingBytes(usize), +} + +#[derive(Debug, thiserror::Error)] +pub enum BgpsecRouterCertificateProfileError { + #[error("resource certificate profile error: {0} (RFC 6487 §4; RFC 8209 §3.1)")] + ResourceCertificate(#[from] ResourceCertificateProfileError), + + #[error("BGPsec router certificate must be an EE certificate (RFC 8209 §3.1)")] + NotEe, + + #[error( + "BGPsec router certificate must contain SubjectKeyIdentifier (RFC 6487 §4.8.2; RFC 8209 §3.3)" + )] + MissingSki, + + #[error( + "BGPsec router certificate must include ExtendedKeyUsage (RFC 8209 §3.1.3.2; RFC 8209 §3.3)" + )] + MissingExtendedKeyUsage, + + #[error( + "BGPsec router certificate ExtendedKeyUsage must be non-critical (RFC 6487 §4.8.4; RFC 8209 §3.1.3.2)" + )] + ExtendedKeyUsageCriticality, + + #[error( + "BGPsec router certificate ExtendedKeyUsage must contain id-kp-bgpsec-router ({OID_KP_BGPSEC_ROUTER}) (RFC 8209 §3.1.3.2; RFC 8209 §3.3)" + )] + MissingBgpsecRouterEku, + + #[error( + "BGPsec router certificate MUST NOT include Subject Information Access (RFC 8209 §3.1.3.3; RFC 8209 §3.3)" + )] + SubjectInfoAccessPresent, + + #[error( + "BGPsec router certificate MUST NOT include IP resources extension (RFC 8209 §3.1.3.4; RFC 8209 §3.3)" + )] + IpResourcesPresent, + + #[error( + "BGPsec router certificate MUST include AS resources extension (RFC 8209 §3.1.3.5; RFC 8209 §3.3)" + )] + AsResourcesMissing, + + #[error( + "BGPsec router certificate AS resources MUST include one or more ASNs (RFC 8209 §3.1.3.5)" + )] + AsResourcesAsnumMissing, + + #[error("BGPsec router certificate AS resources MUST NOT use inherit (RFC 8209 §3.1.3.5)")] + AsResourcesInherit, + + #[error( + "BGPsec router certificate AS resources MUST contain explicit ASNs, not ranges (RFC 8209 §3.1.3.5)" + )] + AsResourcesRangeNotAllowed, + + #[error( + "BGPsec router certificate subjectPublicKeyInfo.algorithm must be id-ecPublicKey ({OID_EC_PUBLIC_KEY}) (RFC 8208 §3.1)" + )] + SpkiAlgorithmNotEcPublicKey, + + #[error( + "BGPsec router certificate subjectPublicKeyInfo.parameters must be secp256r1 ({OID_SECP256R1}) (RFC 8208 §3.1)" + )] + SpkiWrongCurve, + + #[error( + "BGPsec router certificate subjectPublicKeyInfo.parameters missing or invalid (RFC 8208 §3.1)" + )] + SpkiParametersMissingOrInvalid, + + #[error( + "BGPsec router certificate subjectPublicKey MUST be uncompressed P-256 ECPoint (RFC 8208 §3.1)" + )] + SpkiEcPointNotUncompressedP256, +} + +#[derive(Debug, thiserror::Error)] +pub enum BgpsecRouterCertificateDecodeError { + #[error("{0}")] + Parse(#[from] BgpsecRouterCertificateParseError), + + #[error("{0}")] + Validate(#[from] BgpsecRouterCertificateProfileError), +} + +#[derive(Debug, thiserror::Error)] +pub enum BgpsecRouterCertificatePathError { + #[error("{0}")] + Decode(#[from] BgpsecRouterCertificateDecodeError), + + #[error("{0}")] + CertPath(#[from] CertPathError), +} + +impl BgpsecRouterCertificate { + pub fn parse_der( + der: &[u8], + ) -> Result { + let (rem, cert) = X509Certificate::from_der(der) + .map_err(|e| BgpsecRouterCertificateParseError::X509(e.to_string()))?; + if !rem.is_empty() { + return Err(BgpsecRouterCertificateParseError::TrailingBytes(rem.len())); + } + let (spki_rem, _spki) = + SubjectPublicKeyInfo::from_der(cert.tbs_certificate.subject_pki.raw) + .map_err(|e| BgpsecRouterCertificateParseError::SpkiParse(e.to_string()))?; + if !spki_rem.is_empty() { + return Err(BgpsecRouterCertificateParseError::SpkiTrailingBytes( + spki_rem.len(), + )); + } + let rc_parsed = ResourceCertificate::parse_der(der)?; + Ok(BgpsecRouterCertificateParsed { rc_parsed }) + } + + pub fn validate_profile(&self) -> Result<(), BgpsecRouterCertificateProfileError> { + Ok(()) + } + + pub fn decode_der(der: &[u8]) -> Result { + Ok(Self::parse_der(der)?.validate_profile()?) + } + + pub fn from_der(der: &[u8]) -> Result { + Self::decode_der(der) + } + + pub fn validate_path_with_prevalidated_issuer( + der: &[u8], + issuer_ca: &ResourceCertificate, + issuer_spki: &SubjectPublicKeyInfo<'_>, + issuer_crl: &crate::model::crl::RpkixCrl, + issuer_crl_revoked_serials: &std::collections::HashSet>, + issuer_ca_rsync_uri: Option<&str>, + issuer_crl_rsync_uri: Option<&str>, + validation_time: time::OffsetDateTime, + ) -> Result { + let cert = Self::decode_der(der)?; + validate_ee_cert_path_with_predecoded_ee( + &cert.resource_cert, + der, + issuer_ca, + issuer_spki, + issuer_crl, + issuer_crl_revoked_serials, + issuer_ca_rsync_uri, + issuer_crl_rsync_uri, + validation_time, + )?; + Ok(cert) + } +} + +impl BgpsecRouterCertificateParsed { + pub fn validate_profile( + self, + ) -> Result { + let rc = self.rc_parsed.validate_profile()?; + rc.validate_rfc6487_profile(ResourceCertificateRole::RouterEe)?; + if rc.kind != ResourceCertKind::Ee { + return Err(BgpsecRouterCertificateProfileError::NotEe); + } + let ski = rc + .tbs + .extensions + .subject_key_identifier + .clone() + .ok_or(BgpsecRouterCertificateProfileError::MissingSki)?; + + if rc.tbs.extensions.subject_info_access.is_some() { + return Err(BgpsecRouterCertificateProfileError::SubjectInfoAccessPresent); + } + if rc.tbs.extensions.ip_resources.is_some() { + return Err(BgpsecRouterCertificateProfileError::IpResourcesPresent); + } + let as_resources = rc + .tbs + .extensions + .as_resources + .as_ref() + .ok_or(BgpsecRouterCertificateProfileError::AsResourcesMissing)?; + let asns = extract_router_asns(as_resources)?; + + let (rem, cert) = X509Certificate::from_der(&rc.raw_der).map_err(|e| { + BgpsecRouterCertificateProfileError::ResourceCertificate( + ResourceCertificateProfileError::InvalidCertificatePolicy(e.to_string()), + ) + })?; + if !rem.is_empty() { + return Err(BgpsecRouterCertificateProfileError::ResourceCertificate( + ResourceCertificateProfileError::InvalidCertificatePolicy(format!( + "trailing bytes after router certificate DER: {}", + rem.len() + )), + )); + } + validate_router_eku(&cert)?; + validate_router_spki(&rc.tbs.subject_public_key_info)?; + + Ok(BgpsecRouterCertificate { + raw_der: rc.raw_der.clone(), + resource_cert: rc.clone(), + subject_key_identifier: ski, + spki_der: rc.tbs.subject_public_key_info.clone(), + asns, + }) + } +} + +fn extract_router_asns( + as_resources: &crate::model::rc::AsResourceSet, +) -> Result, BgpsecRouterCertificateProfileError> { + let asnum = as_resources + .asnum + .as_ref() + .ok_or(BgpsecRouterCertificateProfileError::AsResourcesAsnumMissing)?; + if matches!(asnum, AsIdentifierChoice::Inherit) + || matches!(as_resources.rdi.as_ref(), Some(AsIdentifierChoice::Inherit)) + { + return Err(BgpsecRouterCertificateProfileError::AsResourcesInherit); + } + let AsIdentifierChoice::AsIdsOrRanges(items) = asnum else { + return Err(BgpsecRouterCertificateProfileError::AsResourcesInherit); + }; + if items.is_empty() { + return Err(BgpsecRouterCertificateProfileError::AsResourcesAsnumMissing); + } + let mut asns = Vec::with_capacity(items.len()); + for item in items { + match item { + AsIdOrRange::Id(v) => asns.push(*v), + AsIdOrRange::Range { .. } => { + return Err(BgpsecRouterCertificateProfileError::AsResourcesRangeNotAllowed); + } + } + } + asns.sort_unstable(); + asns.dedup(); + Ok(asns) +} + +fn validate_router_eku( + cert: &X509Certificate<'_>, +) -> Result<(), BgpsecRouterCertificateProfileError> { + let mut matches = cert + .tbs_certificate + .extensions() + .iter() + .filter(|ext| ext.oid.as_bytes() == OID_EXTENDED_KEY_USAGE_RAW); + let Some(ext) = matches.next() else { + return Err(BgpsecRouterCertificateProfileError::MissingExtendedKeyUsage); + }; + if matches.next().is_some() { + return Err(BgpsecRouterCertificateProfileError::MissingExtendedKeyUsage); + } + if ext.critical { + return Err(BgpsecRouterCertificateProfileError::ExtendedKeyUsageCriticality); + } + let ParsedExtension::ExtendedKeyUsage(eku) = ext.parsed_extension() else { + return Err(BgpsecRouterCertificateProfileError::MissingExtendedKeyUsage); + }; + let found = eku + .other + .iter() + .any(|oid| oid.to_id_string() == OID_KP_BGPSEC_ROUTER); + if !found { + return Err(BgpsecRouterCertificateProfileError::MissingBgpsecRouterEku); + } + Ok(()) +} + +fn validate_router_spki(spki_der: &[u8]) -> Result<(), BgpsecRouterCertificateProfileError> { + let (rem, spki) = SubjectPublicKeyInfo::from_der(spki_der) + .map_err(|_| BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid)?; + if !rem.is_empty() { + return Err(BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid); + } + if spki.algorithm.algorithm.to_id_string() != OID_EC_PUBLIC_KEY { + return Err(BgpsecRouterCertificateProfileError::SpkiAlgorithmNotEcPublicKey); + } + let Some(params) = spki.algorithm.parameters.as_ref() else { + return Err(BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid); + }; + if params.header.tag().0 != 0x06 { + return Err(BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid); + } + let mut der = Vec::with_capacity(params.data.len() + 2); + der.push(0x06); + if params.data.len() >= 0x80 { + return Err(BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid); + } + der.push(params.data.len() as u8); + der.extend_from_slice(params.data); + let (prem, oid) = der_parser::der::parse_der_oid(&der) + .map_err(|_| BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid)?; + if !prem.is_empty() { + return Err(BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid); + } + let curve = oid + .as_oid_val() + .map_err(|_| BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid)? + .to_string(); + if curve != OID_SECP256R1 { + return Err(BgpsecRouterCertificateProfileError::SpkiWrongCurve); + } + let parsed = spki + .parsed() + .map_err(|_| BgpsecRouterCertificateProfileError::SpkiEcPointNotUncompressedP256)?; + let PublicKey::EC(ec) = parsed else { + return Err(BgpsecRouterCertificateProfileError::SpkiAlgorithmNotEcPublicKey); + }; + if ec.data().len() != 65 || ec.data().first() != Some(&0x04) { + return Err(BgpsecRouterCertificateProfileError::SpkiEcPointNotUncompressedP256); + } + Ok(()) +} diff --git a/src/model/signed_object.rs b/src/model/signed_object.rs new file mode 100644 index 0000000..9622fbe --- /dev/null +++ b/src/model/signed_object.rs @@ -0,0 +1,27 @@ +use crate::model::common::{Asn1TimeEncoding, Asn1TimeUtc, DerReader, der_uint_from_bytes}; +use crate::model::oid::{ + OID_AD_SIGNED_OBJECT, OID_CMS_ATTR_CONTENT_TYPE, OID_CMS_ATTR_CONTENT_TYPE_RAW, + OID_CMS_ATTR_MESSAGE_DIGEST, OID_CMS_ATTR_MESSAGE_DIGEST_RAW, OID_CMS_ATTR_SIGNING_TIME, + OID_CMS_ATTR_SIGNING_TIME_RAW, OID_CT_ASPA, OID_CT_ASPA_RAW, OID_CT_ROUTE_ORIGIN_AUTHZ, + OID_CT_ROUTE_ORIGIN_AUTHZ_RAW, OID_CT_RPKI_MANIFEST, OID_CT_RPKI_MANIFEST_RAW, + OID_KEY_USAGE_RAW, OID_RSA_ENCRYPTION, OID_RSA_ENCRYPTION_RAW, OID_SHA256, OID_SHA256_RAW, + OID_SHA256_WITH_RSA_ENCRYPTION, OID_SHA256_WITH_RSA_ENCRYPTION_RAW, OID_SIGNED_DATA, + OID_SIGNED_DATA_RAW, OID_SUBJECT_INFO_ACCESS, +}; +use crate::model::rc::{ResourceCertificate, ResourceCertificateRole, SubjectInfoAccess}; +use asn1_rs::{Any, Class, FromBer, FromDer as Asn1FromDer, Header, Tag}; +use ring::digest; +use x509_parser::extensions::ParsedExtension; +use x509_parser::prelude::X509Certificate; +use x509_parser::public_key::PublicKey; +use x509_parser::x509::SubjectPublicKeyInfo; + +include!("signed_object/types_errors.rs"); +include!("signed_object/signed_object_impl.rs"); +include!("signed_object/cms_reader.rs"); +include!("signed_object/parsed_profile.rs"); +include!("signed_object/signed_attrs.rs"); + +#[cfg(test)] +#[path = "signed_object/tests.rs"] +mod tests; diff --git a/src/model/signed_object/cms_reader.rs b/src/model/signed_object/cms_reader.rs new file mode 100644 index 0000000..461f094 --- /dev/null +++ b/src/model/signed_object/cms_reader.rs @@ -0,0 +1,172 @@ +// CMS reader and BER/DER content parsing primitives. + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum CmsParseMode { + BerCompatible, + DerStrict, +} + +struct CmsReader<'a> { + buf: &'a [u8], + mode: CmsParseMode, +} + +impl<'a> CmsReader<'a> { + fn new(buf: &'a [u8], mode: CmsParseMode) -> Self { + Self { buf, mode } + } + + fn is_empty(&self) -> bool { + self.buf.is_empty() + } + + fn remaining_len(&self) -> usize { + self.buf.len() + } + + fn peek_tag(&self) -> Result { + let (_rem, any) = parse_any(self.buf, self.mode)?; + header_to_single_byte_tag(&any.header) + } + + fn take_any(&mut self) -> Result<(u8, &'a [u8]), String> { + let (rem, any) = parse_any(self.buf, self.mode)?; + let tag = header_to_single_byte_tag(&any.header)?; + self.buf = rem; + Ok((tag, any.data)) + } + + fn take_any_full(&mut self) -> Result<(u8, &'a [u8], &'a [u8]), String> { + let (rem, any) = parse_any(self.buf, self.mode)?; + let consumed = self.buf.len() - rem.len(); + let full = &self.buf[..consumed]; + let tag = header_to_single_byte_tag(&any.header)?; + self.buf = rem; + Ok((tag, full, any.data)) + } + + fn skip_any(&mut self) -> Result<(), String> { + let _ = self.take_any()?; + Ok(()) + } + + fn take_tag(&mut self, expected_tag: u8) -> Result<&'a [u8], String> { + let (tag, value) = self.take_any()?; + if tag != expected_tag { + return Err(format!( + "unexpected tag: got 0x{tag:02X}, expected 0x{expected_tag:02X}" + )); + } + Ok(value) + } + + fn take_sequence(&mut self) -> Result, String> { + let value = self.take_tag(0x30)?; + Ok(CmsReader::new(value, self.mode)) + } + + fn take_octet_string(&mut self) -> Result, String> { + let (rem, any) = parse_any(self.buf, self.mode)?; + let tag = header_to_single_byte_tag(&any.header)?; + if self.mode == CmsParseMode::DerStrict && tag != 0x04 { + return Err(format!( + "unexpected tag in DER strict mode: got 0x{tag:02X}, expected 0x04" + )); + } + if tag != 0x04 && tag != 0x24 { + return Err(format!("unexpected tag: got 0x{tag:02X}, expected 0x04")); + } + let octets = flatten_octet_string(any, self.mode)?; + self.buf = rem; + Ok(octets) + } + + fn take_uint_u64(&mut self) -> Result { + let value = self.take_tag(0x02)?; + der_uint_from_bytes(value) + } + + fn take_explicit_der(&mut self, expected_outer_tag: u8) -> Result<&'a [u8], String> { + let inner_der = self.take_tag(expected_outer_tag)?; + let (_tag, _value, rem) = cms_take_tlv(inner_der, self.mode)?; + if !rem.is_empty() { + return Err("trailing bytes inside EXPLICIT value".into()); + } + Ok(inner_der) + } +} + +fn parse_signed_object_content_info( + raw_der: &[u8], + parse_der: &[u8], + mode: CmsParseMode, +) -> Result { + let mut r = CmsReader::new(parse_der, mode); + let mut content_info_seq = r + .take_sequence() + .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; + if !r.is_empty() { + return Err(SignedObjectParseError::TrailingBytes(r.remaining_len())); + } + + let content_type = take_oid_string(&mut content_info_seq)?; + let signed_data = parse_signed_data_from_contentinfo_cursor(&mut content_info_seq)?; + if !content_info_seq.is_empty() { + return Err(SignedObjectParseError::Parse( + "ContentInfo must be a SEQUENCE of 2 elements".into(), + )); + } + + Ok(RpkiSignedObjectParsed { + raw_der: raw_der.to_vec(), + content_info_content_type: content_type, + signed_data, + }) +} + +fn parse_any<'a>(input: &'a [u8], mode: CmsParseMode) -> Result<(&'a [u8], Any<'a>), String> { + match mode { + CmsParseMode::BerCompatible => { + Any::from_ber(input).map_err(|e| format!("BER parse error: {e}")) + } + CmsParseMode::DerStrict => { + Any::from_der(input).map_err(|e| format!("DER parse error: {e}")) + } + } +} + +fn header_to_single_byte_tag(header: &Header<'_>) -> Result { + let tag_no = header.tag().0; + if tag_no > 30 { + return Err(format!("high-tag-number form not supported: {tag_no}")); + } + Ok(((header.class() as u8) << 6) + | if header.constructed() { 0x20 } else { 0x00 } + | tag_no as u8) +} + +fn cms_take_tlv(input: &[u8], mode: CmsParseMode) -> Result<(u8, &[u8], &[u8]), String> { + let (rem, any) = parse_any(input, mode)?; + let tag = header_to_single_byte_tag(&any.header)?; + Ok((tag, any.data, rem)) +} + +fn flatten_octet_string(any: Any<'_>, mode: CmsParseMode) -> Result, String> { + if any.class() != Class::Universal || any.tag() != Tag::OctetString { + return Err("expected OCTET STRING".into()); + } + if !any.header.constructed() { + return Ok(any.data.to_vec()); + } + if mode == CmsParseMode::DerStrict { + return Err("constructed OCTET STRING is not allowed in DER strict mode".into()); + } + let mut out = Vec::new(); + let mut input = any.data; + while !input.is_empty() { + let (rem, child) = Any::from_ber(input).map_err(|e| format!("BER parse error: {e}"))?; + out.extend(flatten_octet_string(child, mode)?); + input = rem; + } + Ok(out) +} diff --git a/src/model/signed_object/parsed_profile.rs b/src/model/signed_object/parsed_profile.rs new file mode 100644 index 0000000..a2d3872 --- /dev/null +++ b/src/model/signed_object/parsed_profile.rs @@ -0,0 +1,542 @@ +// SignedData profile validation and EE certificate checks. + +impl RpkiSignedObjectParsed { + pub fn validate_profile(self) -> Result { + self.validate_profile_with_strict_name(false) + } + + pub fn validate_profile_with_strict_name( + self, + strict_name: bool, + ) -> Result { + if self.content_info_content_type != OID_SIGNED_DATA { + return Err(SignedObjectValidateError::InvalidContentInfoContentType( + self.content_info_content_type, + )); + } + + let signed_data = validate_signed_data_profile(self.signed_data, strict_name)?; + + Ok(RpkiSignedObject { + raw_der: self.raw_der, + content_info_content_type: OID_SIGNED_DATA.to_string(), + signed_data, + }) + } +} + +fn parse_signed_data_from_contentinfo_cursor( + seq: &mut CmsReader<'_>, +) -> Result { + let inner_der = seq.take_explicit_der(0xA0).map_err(|_e| { + SignedObjectParseError::Parse("ContentInfo.content must be [0] EXPLICIT".into()) + })?; + let mut r = CmsReader::new(inner_der, seq.mode); + let signed_data_seq = r + .take_sequence() + .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; + if !r.is_empty() { + return Err(SignedObjectParseError::Parse( + "trailing bytes inside ContentInfo.content".into(), + )); + } + parse_signed_data_cursor(signed_data_seq) +} + +fn parse_signed_data_cursor( + mut seq: CmsReader<'_>, +) -> Result { + let version = seq + .take_uint_u64() + .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; + + let digest_set_bytes = seq + .take_tag(0x31) + .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; + let mut digest_set = CmsReader::new(digest_set_bytes, seq.mode); + let mut digest_algorithms: Vec = Vec::new(); + while !digest_set.is_empty() { + let alg = digest_set + .take_sequence() + .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; + let (oid, params_ok) = parse_algorithm_identifier_cursor(alg)?; + digest_algorithms.push(AlgorithmIdentifierParsed { oid, params_ok }); + } + + let encap_content_info = parse_encapsulated_content_info_cursor( + seq.take_sequence() + .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?, + )?; + + let mut certificates: Option>> = None; + let mut crls_present = false; + let mut signer_infos: Option> = None; + + while !seq.is_empty() { + let tag = seq + .peek_tag() + .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; + match tag { + 0xA0 => { + if certificates.is_some() { + return Err(SignedObjectParseError::Parse( + "SignedData.certificates appears more than once".into(), + )); + } + let content = seq + .take_tag(0xA0) + .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; + certificates = Some(split_der_objects(content, seq.mode)?); + } + 0xA1 => { + crls_present = true; + seq.skip_any() + .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; + } + 0x31 => { + if signer_infos.is_some() { + return Err(SignedObjectParseError::Parse( + "SignedData.signerInfos appears more than once".into(), + )); + } + let set_bytes = seq + .take_tag(0x31) + .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; + signer_infos = Some(parse_signer_infos_set_cursor(set_bytes, seq.mode)?); + } + _ => { + return Err(SignedObjectParseError::Parse( + "unexpected field in SignedData".into(), + )); + } + } + } + + let signer_infos = signer_infos + .ok_or_else(|| SignedObjectParseError::Parse("SignedData.signerInfos missing".into()))?; + + Ok(SignedDataParsed { + version, + digest_algorithms, + encap_content_info, + certificates, + crls_present, + signer_infos, + }) +} + +fn parse_encapsulated_content_info_cursor( + mut seq: CmsReader<'_>, +) -> Result { + if seq.is_empty() { + return Err(SignedObjectParseError::Parse( + "EncapsulatedContentInfo must be SEQUENCE of 1..2".into(), + )); + } + + let econtent_type = take_oid_string(&mut seq)?; + + let econtent = if seq.is_empty() { + None + } else { + let inner_der = seq.take_explicit_der(0xA0).map_err(|_e| { + SignedObjectParseError::Parse( + "EncapsulatedContentInfo.eContent must be [0] EXPLICIT".into(), + ) + })?; + let mut inner = CmsReader::new(inner_der, seq.mode); + let octets = inner + .take_octet_string() + .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; + if !inner.is_empty() { + return Err(SignedObjectParseError::Parse( + "trailing bytes inside EncapsulatedContentInfo.eContent".into(), + )); + } + Some(octets) + }; + if !seq.is_empty() { + return Err(SignedObjectParseError::Parse( + "EncapsulatedContentInfo must be SEQUENCE of 1..2".into(), + )); + } + + Ok(EncapsulatedContentInfoParsed { + econtent_type, + econtent, + }) +} + +fn split_der_objects( + mut input: &[u8], + mode: CmsParseMode, +) -> Result>, SignedObjectParseError> { + let mut out: Vec> = Vec::new(); + while !input.is_empty() { + let (_tag, _value, rem) = + cms_take_tlv(input, mode).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; + let consumed = input.len() - rem.len(); + out.push(input[..consumed].to_vec()); + input = rem; + } + Ok(out) +} + +fn parse_signer_infos_set_cursor( + set_bytes: &[u8], + mode: CmsParseMode, +) -> Result, SignedObjectParseError> { + let mut set = CmsReader::new(set_bytes, mode); + let mut out: Vec = Vec::new(); + while !set.is_empty() { + let si = set + .take_sequence() + .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; + out.push(parse_signer_info_cursor(si)?); + } + Ok(out) +} + +fn validate_ee_certificate( + der: &[u8], + strict_name: bool, +) -> Result { + let (rem, cert) = X509Certificate::from_der(der) + .map_err(|e| SignedObjectValidateError::EeCertificateParse(e.to_string()))?; + if !rem.is_empty() { + return Err(SignedObjectValidateError::EeCertificateParse(format!( + "trailing bytes after EE certificate DER: {}", + rem.len() + ))); + } + + let rc = match ResourceCertificate::from_der(der) { + Ok(v) => v, + Err(e) => { + return match e { + crate::model::rc::ResourceCertificateDecodeError::Validate( + crate::model::rc::ResourceCertificateProfileError::SignedObjectSiaNotUri, + ) => Err(SignedObjectValidateError::EeCertificateSignedObjectSiaNotUri), + crate::model::rc::ResourceCertificateDecodeError::Validate( + crate::model::rc::ResourceCertificateProfileError::SignedObjectSiaNoRsync, + ) => Err(SignedObjectValidateError::EeCertificateSignedObjectSiaNoRsync), + _ => Err(SignedObjectValidateError::EeCertificateParse(e.to_string())), + }; + } + }; + rc.validate_rfc6487_profile(ResourceCertificateRole::SignedObjectEe) + .map_err(|e| SignedObjectValidateError::EeCertificateParse(e.to_string()))?; + if strict_name { + rc.validate_strict_name_profile() + .map_err(|e| SignedObjectValidateError::EeCertificateParse(e.to_string()))?; + } + + let ski = rc + .tbs + .extensions + .subject_key_identifier + .clone() + .ok_or(SignedObjectValidateError::EeCertificateMissingSki)?; + + let spki_der = rc.tbs.subject_public_key_info.clone(); + let (rem, spki) = SubjectPublicKeyInfo::from_der(&spki_der) + .map_err(|e| SignedObjectValidateError::EeCertificateParse(e.to_string()))?; + if !rem.is_empty() { + return Err(SignedObjectValidateError::EeCertificateParse( + "trailing bytes after EE SubjectPublicKeyInfo DER".to_string(), + )); + } + let parsed_pk = spki.parsed().map_err(|_e| { + SignedObjectValidateError::EeCertificateParse( + "unsupported EE public key algorithm".to_string(), + ) + })?; + let (rsa_public_modulus, rsa_public_exponent) = match parsed_pk { + PublicKey::RSA(rsa) => { + let modulus = strip_leading_zeros(rsa.modulus).to_vec(); + let exponent = strip_leading_zeros(rsa.exponent).to_vec(); + let _ = rsa.try_exponent().map_err(|_e| { + SignedObjectValidateError::EeCertificateParse("invalid EE RSA exponent".to_string()) + })?; + (modulus, exponent) + } + _ => { + return Err(SignedObjectValidateError::EeCertificateParse( + "unsupported EE public key algorithm".to_string(), + )); + } + }; + + let sia = rc + .tbs + .extensions + .subject_info_access + .as_ref() + .ok_or(SignedObjectValidateError::EeCertificateMissingSia)?; + let signed_object_uris: Vec = match sia { + SubjectInfoAccess::Ee(ee) => ee.signed_object_uris.clone(), + SubjectInfoAccess::Ca(_ca) => Vec::new(), + }; + if signed_object_uris.is_empty() { + return Err(SignedObjectValidateError::EeCertificateMissingSignedObjectSia); + } + if !signed_object_uris.iter().any(|u| u.starts_with("rsync://")) { + return Err(SignedObjectValidateError::EeCertificateSignedObjectSiaNoRsync); + } + + Ok(ResourceEeCertificate { + raw_der: der.to_vec(), + subject_key_identifier: ski, + spki_der, + rsa_public_modulus, + rsa_public_exponent, + tbs_certificate_der: cert.tbs_certificate.as_ref().to_vec(), + signature_bytes: cert.signature_value.data.to_vec(), + key_usage_summary: summarize_ee_key_usage(&cert), + sia_signed_object_uris: signed_object_uris, + resource_cert: rc, + }) +} + +fn summarize_ee_key_usage(cert: &X509Certificate<'_>) -> EeKeyUsageSummary { + for ext in cert.extensions() { + if ext.oid.as_bytes() == OID_KEY_USAGE_RAW { + match ext.parsed_extension() { + ParsedExtension::KeyUsage(ku) => { + if !ext.critical { + return EeKeyUsageSummary::NotCritical; + } + let ok = ku.digital_signature() + && !ku.key_cert_sign() + && !ku.crl_sign() + && !ku.non_repudiation() + && !ku.key_encipherment() + && !ku.data_encipherment() + && !ku.key_agreement() + && !ku.encipher_only() + && !ku.decipher_only(); + return if ok { + EeKeyUsageSummary::DigitalSignatureOnly + } else { + EeKeyUsageSummary::InvalidBits + }; + } + other => { + return EeKeyUsageSummary::ParseError(format!( + "unexpected parsed keyUsage extension: {other:?}" + )); + } + } + } + } + + EeKeyUsageSummary::Missing +} + +fn parse_signer_info_cursor( + mut seq: CmsReader<'_>, +) -> Result { + let version = seq + .take_uint_u64() + .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; + + let (sid_tag, sid_bytes) = seq + .take_any() + .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; + let sid = if (sid_tag & 0xC0) == 0x80 && (sid_tag & 0x1F) == 0 { + SignerIdentifierParsed::SubjectKeyIdentifier(sid_bytes.to_vec()) + } else { + SignerIdentifierParsed::Other + }; + + let digest_alg_seq = seq + .take_sequence() + .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; + let (digest_oid, digest_params_ok) = parse_algorithm_identifier_cursor(digest_alg_seq)?; + let digest_algorithm = AlgorithmIdentifierParsed { + oid: digest_oid, + params_ok: digest_params_ok, + }; + + let mut signed_attrs_content: Option> = None; + let mut signed_attrs_der_for_signature: Option> = None; + + if seq + .peek_tag() + .map_err(|e| SignedObjectParseError::Parse(e.to_string()))? + == 0xA0 + { + let (tag, full_tlv, value) = seq + .take_any_full() + .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; + if tag != 0xA0 { + return Err(SignedObjectParseError::Parse( + "SignerInfo.signedAttrs must be [0] IMPLICIT".into(), + )); + } + signed_attrs_content = Some(value.to_vec()); + signed_attrs_der_for_signature = Some(make_signed_attrs_der_for_signature(full_tlv)?); + } + + let sig_alg_seq = seq + .take_sequence() + .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; + let (signature_oid, signature_params_ok) = parse_algorithm_identifier_cursor(sig_alg_seq)?; + let signature_algorithm = AlgorithmIdentifierParsed { + oid: signature_oid, + params_ok: signature_params_ok, + }; + + let signature = seq + .take_octet_string() + .map_err(|e| SignedObjectParseError::Parse(e.to_string()))? + .to_vec(); + + let unsigned_attrs_present = !seq.is_empty(); + + Ok(SignerInfoParsed { + version, + sid, + digest_algorithm, + signature_algorithm, + signed_attrs_content, + signed_attrs_der_for_signature, + unsigned_attrs_present, + signature, + }) +} + +fn validate_signed_data_profile( + signed_data: SignedDataParsed, + strict_name: bool, +) -> Result { + if signed_data.version != 3 { + return Err(SignedObjectValidateError::InvalidSignedDataVersion( + signed_data.version, + )); + } + + if signed_data.digest_algorithms.len() != 1 { + return Err(SignedObjectValidateError::InvalidDigestAlgorithmsCount( + signed_data.digest_algorithms.len(), + )); + } + let digest_alg = &signed_data.digest_algorithms[0]; + if digest_alg.oid != OID_SHA256 { + return Err(SignedObjectValidateError::InvalidDigestAlgorithm( + digest_alg.oid.clone(), + )); + } + + if signed_data.crls_present { + return Err(SignedObjectValidateError::CrlsPresent); + } + + let econtent = signed_data + .encap_content_info + .econtent + .clone() + .ok_or(SignedObjectValidateError::EContentMissing)?; + if econtent.is_empty() { + return Err(SignedObjectValidateError::EContentMissing); + } + let encap_content_info = EncapsulatedContentInfo { + econtent_type: signed_data.encap_content_info.econtent_type.clone(), + econtent: econtent.clone(), + }; + + let certs = signed_data + .certificates + .as_ref() + .ok_or(SignedObjectValidateError::CertificatesMissing)?; + if certs.len() != 1 { + return Err(SignedObjectValidateError::InvalidCertificatesCount( + certs.len(), + )); + } + let ee = validate_ee_certificate(&certs[0], strict_name)?; + + if signed_data.signer_infos.len() != 1 { + return Err(SignedObjectValidateError::InvalidSignerInfosCount( + signed_data.signer_infos.len(), + )); + } + let signer = &signed_data.signer_infos[0]; + + if signer.version != 3 { + return Err(SignedObjectValidateError::InvalidSignerInfoVersion( + signer.version, + )); + } + let sid_ski = match &signer.sid { + SignerIdentifierParsed::SubjectKeyIdentifier(ski) => ski.clone(), + SignerIdentifierParsed::Other => { + return Err(SignedObjectValidateError::InvalidSignerIdentifier); + } + }; + + if signer.digest_algorithm.oid != OID_SHA256 { + return Err(SignedObjectValidateError::InvalidSignerInfoDigestAlgorithm( + signer.digest_algorithm.oid.clone(), + )); + } + + let signed_attrs_content = signer + .signed_attrs_content + .as_deref() + .ok_or(SignedObjectValidateError::SignedAttrsMissing)?; + let signed_attrs_der_for_signature = signer + .signed_attrs_der_for_signature + .clone() + .ok_or(SignedObjectValidateError::SignedAttrsMissing)?; + let signed_attrs = parse_signed_attrs_implicit(signed_attrs_content)?; + + if signer.unsigned_attrs_present { + return Err(SignedObjectValidateError::UnsignedAttrsPresent); + } + + if !signer.signature_algorithm.params_ok { + return Err(SignedObjectValidateError::InvalidSignatureAlgorithmParameters); + } + let signature_algorithm = signer.signature_algorithm.oid.clone(); + if signature_algorithm != OID_RSA_ENCRYPTION + && signature_algorithm != OID_SHA256_WITH_RSA_ENCRYPTION + { + return Err(SignedObjectValidateError::InvalidSignatureAlgorithm( + signature_algorithm, + )); + } + + if sid_ski != ee.subject_key_identifier { + return Err(SignedObjectValidateError::SidSkiMismatch); + } + if signed_attrs.content_type != encap_content_info.econtent_type { + return Err(SignedObjectValidateError::ContentTypeAttrMismatch { + econtent_type: encap_content_info.econtent_type.clone(), + attr_content_type: signed_attrs.content_type.clone(), + }); + } + + let computed = digest::digest(&digest::SHA256, &encap_content_info.econtent); + if computed.as_ref() != signed_attrs.message_digest.as_slice() { + return Err(SignedObjectValidateError::MessageDigestMismatch); + } + + Ok(SignedDataProfiled { + version: 3, + digest_algorithms: vec![OID_SHA256.to_string()], + encap_content_info, + certificates: vec![ee.clone()], + crls_present: false, + signer_infos: vec![SignerInfoProfiled { + version: 3, + sid_ski, + digest_algorithm: OID_SHA256.to_string(), + signature_algorithm: signer.signature_algorithm.oid.clone(), + signed_attrs, + unsigned_attrs_present: false, + signature: signer.signature.clone(), + signed_attrs_der_for_signature, + }], + }) +} diff --git a/src/model/signed_object/signed_attrs.rs b/src/model/signed_object/signed_attrs.rs new file mode 100644 index 0000000..807aa41 --- /dev/null +++ b/src/model/signed_object/signed_attrs.rs @@ -0,0 +1,344 @@ +// Signed attributes, signing-time, and algorithm parsing helpers. + +fn parse_signed_attrs_implicit( + input: &[u8], +) -> Result { + let mut content_type: Option = None; + let mut message_digest: Option> = None; + let mut signing_time: Option = None; + + fn count_elements(mut r: DerReader<'_>) -> Result { + let mut n = 0usize; + while !r.is_empty() { + r.skip_any()?; + n += 1; + } + Ok(n) + } + + let mut remaining = DerReader::new(input); + while !remaining.is_empty() { + let mut attr = remaining + .take_sequence() + .map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?; + + let oid_bytes = attr + .take_tag(0x06) + .map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?; + let oid = oid_value_bytes_to_string(oid_bytes); + + let values_bytes = attr + .take_tag(0x31) + .map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?; + if !attr.is_empty() { + return Err(SignedObjectValidateError::SignedAttrsParse( + "Attribute must be SEQUENCE of 2".into(), + )); + } + + let mut values = DerReader::new(values_bytes); + let count = if values.is_empty() { + 0 + } else { + values + .skip_any() + .map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?; + if values.is_empty() { + 1 + } else { + 1 + count_elements(values) + .map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))? + } + }; + if count != 1 { + return Err( + SignedObjectValidateError::InvalidSignedAttributeValuesCount { oid, count }, + ); + } + + // Re-parse the sole value. + let mut values = DerReader::new(values_bytes); + let (val_tag, val_bytes) = values + .take_any() + .map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?; + + match oid.as_str() { + OID_CMS_ATTR_CONTENT_TYPE => { + if content_type.is_some() { + return Err(SignedObjectValidateError::DuplicateSignedAttribute(oid)); + } + if val_tag != 0x06 { + return Err(SignedObjectValidateError::SignedAttrsParse( + "content-type attr value must be OBJECT IDENTIFIER".into(), + )); + } + content_type = Some(oid_value_bytes_to_string(val_bytes)); + } + OID_CMS_ATTR_MESSAGE_DIGEST => { + if message_digest.is_some() { + return Err(SignedObjectValidateError::DuplicateSignedAttribute(oid)); + } + if val_tag != 0x04 { + return Err(SignedObjectValidateError::SignedAttrsParse( + "message-digest attr value must be OCTET STRING".into(), + )); + } + message_digest = Some(val_bytes.to_vec()); + } + OID_CMS_ATTR_SIGNING_TIME => { + if signing_time.is_some() { + return Err(SignedObjectValidateError::DuplicateSignedAttribute(oid)); + } + signing_time = Some(parse_signing_time_value_tlv(val_tag, val_bytes)?); + } + _ => { + return Err(SignedObjectValidateError::UnsupportedSignedAttribute(oid)); + } + } + } + + Ok(SignedAttrsProfiled { + content_type: content_type + .ok_or(SignedObjectValidateError::SignedAttrsContentTypeMissing)?, + message_digest: message_digest + .ok_or(SignedObjectValidateError::SignedAttrsMessageDigestMissing)?, + signing_time: signing_time + .ok_or(SignedObjectValidateError::SignedAttrsSigningTimeMissing)?, + other_attrs_present: false, + }) +} + +fn parse_signing_time_value_tlv( + tag: u8, + value: &[u8], +) -> Result { + match tag { + 0x17 => Ok(Asn1TimeUtc { + utc: parse_utctime(value)?, + encoding: Asn1TimeEncoding::UtcTime, + }), + 0x18 => Ok(Asn1TimeUtc { + utc: parse_generalized_time(value)?, + encoding: Asn1TimeEncoding::GeneralizedTime, + }), + _ => Err(SignedObjectValidateError::InvalidSigningTimeValue), + } +} + +fn make_signed_attrs_der_for_signature(full_tlv: &[u8]) -> Result, SignedObjectParseError> { + // We need the DER encoding of SignedAttributes (SET OF Attribute) as signature input. + // The SignedAttributes field in SignerInfo is `[0] IMPLICIT`, so the on-wire bytes start with + // a context-specific constructed tag (0xA0 for tag 0). For signature verification, this tag + // is replaced with the universal SET tag (0x31), leaving length+content unchanged. + // + let mut cs_der = full_tlv.to_vec(); + if cs_der.is_empty() { + return Err(SignedObjectParseError::Parse( + "signedAttrs encoding is empty".into(), + )); + } + // The first byte should be the context-specific tag (0xA0) for [0] constructed. + // Replace it with universal SET (0x31) for signature input. + cs_der[0] = 0x31; + Ok(cs_der) +} + +fn take_oid_string(seq: &mut CmsReader<'_>) -> Result { + let oid = seq + .take_tag(0x06) + .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; + Ok(oid_value_bytes_to_string(oid)) +} + +fn oid_value_bytes_to_string(oid_value: &[u8]) -> String { + if oid_value == OID_SHA256_RAW { + return OID_SHA256.to_string(); + } + if oid_value == OID_SIGNED_DATA_RAW { + return OID_SIGNED_DATA.to_string(); + } + if oid_value == OID_CMS_ATTR_CONTENT_TYPE_RAW { + return OID_CMS_ATTR_CONTENT_TYPE.to_string(); + } + if oid_value == OID_CMS_ATTR_MESSAGE_DIGEST_RAW { + return OID_CMS_ATTR_MESSAGE_DIGEST.to_string(); + } + if oid_value == OID_CMS_ATTR_SIGNING_TIME_RAW { + return OID_CMS_ATTR_SIGNING_TIME.to_string(); + } + if oid_value == OID_RSA_ENCRYPTION_RAW { + return OID_RSA_ENCRYPTION.to_string(); + } + if oid_value == OID_SHA256_WITH_RSA_ENCRYPTION_RAW { + return OID_SHA256_WITH_RSA_ENCRYPTION.to_string(); + } + if oid_value == OID_CT_RPKI_MANIFEST_RAW { + return OID_CT_RPKI_MANIFEST.to_string(); + } + if oid_value == OID_CT_ROUTE_ORIGIN_AUTHZ_RAW { + return OID_CT_ROUTE_ORIGIN_AUTHZ.to_string(); + } + if oid_value == OID_CT_ASPA_RAW { + return OID_CT_ASPA.to_string(); + } + decode_oid_to_dotted_string(oid_value) +} + +fn decode_oid_to_dotted_string(value: &[u8]) -> String { + if value.is_empty() { + return "".into(); + } + let first = value[0]; + let a = (first / 40) as u32; + let b = (first % 40) as u32; + let mut out = String::new(); + out.push_str(&a.to_string()); + out.push('.'); + out.push_str(&b.to_string()); + + let mut idx = 1usize; + while idx < value.len() { + let mut v: u32 = 0; + loop { + if idx >= value.len() { + out.push_str("."); + return out; + } + let byte = value[idx]; + idx += 1; + v = (v << 7) | (byte as u32 & 0x7F); + if (byte & 0x80) == 0 { + break; + } + } + out.push('.'); + out.push_str(&v.to_string()); + } + out +} + +fn parse_algorithm_identifier_cursor( + mut seq: CmsReader<'_>, +) -> Result<(String, bool), SignedObjectParseError> { + if seq.is_empty() { + return Err(SignedObjectParseError::Parse( + "AlgorithmIdentifier must be SEQUENCE of 1..2".into(), + )); + } + let oid = take_oid_string(&mut seq)?; + let params_ok = if seq.is_empty() { + true + } else { + let (tag, value) = seq + .take_any() + .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; + tag == 0x05 && value.is_empty() + }; + if !seq.is_empty() { + return Err(SignedObjectParseError::Parse( + "AlgorithmIdentifier must be SEQUENCE of 1..2".into(), + )); + } + Ok((oid, params_ok)) +} + +fn parse_utctime(value: &[u8]) -> Result { + let s = std::str::from_utf8(value) + .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?; + if !s.ends_with('Z') { + return Err(SignedObjectValidateError::InvalidSigningTimeValue); + } + let digits = &s[..s.len() - 1]; + if digits.len() != 10 && digits.len() != 12 { + return Err(SignedObjectValidateError::InvalidSigningTimeValue); + } + if !digits.as_bytes().iter().all(|b| b.is_ascii_digit()) { + return Err(SignedObjectValidateError::InvalidSigningTimeValue); + } + let yy: i32 = digits[0..2] + .parse() + .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?; + let year = if yy <= 49 { 2000 + yy } else { 1900 + yy }; + let mon: u8 = digits[2..4] + .parse() + .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?; + let day: u8 = digits[4..6] + .parse() + .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?; + let hour: u8 = digits[6..8] + .parse() + .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?; + let min: u8 = digits[8..10] + .parse() + .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?; + let sec: u8 = if digits.len() == 12 { + digits[10..12] + .parse() + .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)? + } else { + 0 + }; + let month = time::Month::try_from(mon) + .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?; + let date = time::Date::from_calendar_date(year, month, day) + .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?; + let time = time::Time::from_hms(hour, min, sec) + .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?; + Ok(time::OffsetDateTime::new_utc(date, time)) +} + +fn parse_generalized_time(value: &[u8]) -> Result { + let s = std::str::from_utf8(value) + .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?; + if !s.ends_with('Z') { + return Err(SignedObjectValidateError::InvalidSigningTimeValue); + } + let digits = &s[..s.len() - 1]; + if digits.len() != 12 && digits.len() != 14 { + return Err(SignedObjectValidateError::InvalidSigningTimeValue); + } + if !digits.as_bytes().iter().all(|b| b.is_ascii_digit()) { + return Err(SignedObjectValidateError::InvalidSigningTimeValue); + } + let year: i32 = digits[0..4] + .parse() + .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?; + let mon: u8 = digits[4..6] + .parse() + .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?; + let day: u8 = digits[6..8] + .parse() + .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?; + let hour: u8 = digits[8..10] + .parse() + .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?; + let min: u8 = digits[10..12] + .parse() + .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?; + let sec: u8 = if digits.len() == 14 { + digits[12..14] + .parse() + .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)? + } else { + 0 + }; + let month = time::Month::try_from(mon) + .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?; + let date = time::Date::from_calendar_date(year, month, day) + .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?; + let time = time::Time::from_hms(hour, min, sec) + .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?; + Ok(time::OffsetDateTime::new_utc(date, time)) +} + +fn strip_leading_zeros(bytes: &[u8]) -> &[u8] { + let mut idx = 0; + while idx < bytes.len() && bytes[idx] == 0 { + idx += 1; + } + if idx == bytes.len() { + &bytes[bytes.len() - 1..] + } else { + &bytes[idx..] + } +} diff --git a/src/model/signed_object/signed_object_impl.rs b/src/model/signed_object/signed_object_impl.rs new file mode 100644 index 0000000..18ff917 --- /dev/null +++ b/src/model/signed_object/signed_object_impl.rs @@ -0,0 +1,118 @@ +// Signed-object decoding and RSA signature verification API. + +impl RpkiSignedObject { + /// Parse a DER-encoded RPKI Signed Object (CMS ContentInfo wrapping SignedData). + /// + /// This performs encoding/structure parsing only. Profile constraints are enforced by + /// `RpkiSignedObjectParsed::validate_profile`. + pub fn parse_der(der: &[u8]) -> Result { + parse_signed_object_content_info(der, der, CmsParseMode::BerCompatible) + } + + pub fn parse_der_strict_cms( + der: &[u8], + ) -> Result { + parse_signed_object_content_info(der, der, CmsParseMode::DerStrict) + } + + /// Return the strict-DER CMS parse error for an object that was otherwise + /// accepted through the normal BER-compatible CMS parser. + /// + /// Callers must only surface this as a compatibility warning after normal + /// decoding and validation have succeeded; a strict parse failure alone + /// does not prove that an arbitrary byte string is an RPKI signed object. + pub fn strict_cms_der_error(der: &[u8]) -> Option { + Self::parse_der_strict_cms(der).err() + } + + /// Decode a DER-encoded RPKI Signed Object (CMS ContentInfo wrapping SignedData) and enforce + /// the profile constraints from RFC 6488 §2-§3 and RFC 9589 §4. + pub fn decode_der(der: &[u8]) -> Result { + let parsed = Self::parse_der(der)?; + Ok(parsed.validate_profile()?) + } + + pub fn decode_der_with_strict_options( + der: &[u8], + strict_cms_der: bool, + strict_name: bool, + ) -> Result { + let parsed = if strict_cms_der { + Self::parse_der_strict_cms(der)? + } else { + Self::parse_der(der)? + }; + Ok(parsed.validate_profile_with_strict_name(strict_name)?) + } + + /// Scheme-A naming for signature verification. + pub fn verify(&self) -> Result<(), SignedObjectVerifyError> { + self.verify_signature() + } + + /// Verify the CMS signature using the embedded EE certificate public key. + pub fn verify_signature(&self) -> Result<(), SignedObjectVerifyError> { + let ee = &self.signed_data.certificates[0]; + + self.verify_signature_with_rsa_components( + &ee.rsa_public_modulus, + &ee.rsa_public_exponent, + ) + } + + /// Verify the CMS signature using a DER-encoded SubjectPublicKeyInfo. + pub fn verify_signature_with_ee_spki_der( + &self, + ee_spki_der: &[u8], + ) -> Result<(), SignedObjectVerifyError> { + let (rem, spki) = SubjectPublicKeyInfo::from_der(ee_spki_der) + .map_err(|e| SignedObjectVerifyError::EeSpkiParse(e.to_string()))?; + if !rem.is_empty() { + return Err(SignedObjectVerifyError::EeSpkiTrailingBytes(rem.len())); + } + self.verify_signature_with_ee_spki(&spki) + } + + /// Verify the CMS signature using a parsed SubjectPublicKeyInfo. + pub fn verify_signature_with_ee_spki( + &self, + ee_spki: &SubjectPublicKeyInfo<'_>, + ) -> Result<(), SignedObjectVerifyError> { + let pk = ee_spki + .parsed() + .map_err(|_e| SignedObjectVerifyError::UnsupportedEePublicKeyAlgorithm)?; + let (n, e) = match pk { + PublicKey::RSA(rsa) => { + let n = strip_leading_zeros(rsa.modulus).to_vec(); + let e = strip_leading_zeros(rsa.exponent).to_vec(); + let _exp = rsa + .try_exponent() + .map_err(|_e| SignedObjectVerifyError::InvalidEeRsaExponent)?; + (n, e) + } + _ => return Err(SignedObjectVerifyError::UnsupportedEePublicKeyAlgorithm), + }; + + self.verify_signature_with_rsa_components(n.as_slice(), e.as_slice()) + } + + fn verify_signature_with_rsa_components( + &self, + modulus: &[u8], + exponent: &[u8], + ) -> Result<(), SignedObjectVerifyError> { + let signer = &self.signed_data.signer_infos[0]; + let msg = &signer.signed_attrs_der_for_signature; + + let pk = ring::signature::RsaPublicKeyComponents { + n: modulus, + e: exponent, + }; + pk.verify( + &ring::signature::RSA_PKCS1_2048_8192_SHA256, + msg, + &signer.signature, + ) + .map_err(|_e| SignedObjectVerifyError::InvalidSignature) + } +} diff --git a/src/model/signed_object/tests.rs b/src/model/signed_object/tests.rs new file mode 100644 index 0000000..f83845a --- /dev/null +++ b/src/model/signed_object/tests.rs @@ -0,0 +1,117 @@ +// Signed-object CMS compatibility and profile tests. + +use super::*; + +#[test] +fn strict_cms_der_rejects_constructed_octet_string_fixture() { + let der = std::fs::read( + crate::test_support::synthetic_repository() + .case_repository("baseline-v1") + .join("child/child.mft"), + ) + .expect("read synthetic manifest"); + let parsed = RpkiSignedObject::parse_der(&der).expect("parse fixture"); + let econtent = parsed + .signed_data + .encap_content_info + .econtent + .clone() + .expect("fixture eContent"); + assert_eq!( + parsed.signed_data.encap_content_info.econtent.as_deref(), + Some(econtent.as_slice()) + ); + + let primitive_octets = der_tlv(0x04, &econtent); + let constructed_octets = same_size_constructed_octet_string(&primitive_octets, &econtent); + let mutated = replace_first_subslice(&der, &primitive_octets, &constructed_octets) + .expect("replace eContent OCTET STRING"); + let compatible = RpkiSignedObject::parse_der(&mutated).expect("BER-compatible parse"); + assert!( + econtent.starts_with( + compatible + .signed_data + .encap_content_info + .econtent + .as_deref() + .expect("compatible eContent") + ) + ); + let err = RpkiSignedObject::parse_der_strict_cms(&mutated) + .expect_err("DER strict rejects constructed OCTET STRING"); + assert!(err.to_string().contains("DER"), "{err}"); + let compatibility_error = RpkiSignedObject::strict_cms_der_error(&mutated) + .expect("BER-compatible fixture must report strict-DER incompatibility"); + assert_eq!(compatibility_error.to_string(), err.to_string()); + assert!(RpkiSignedObject::strict_cms_der_error(&der).is_none()); +} + +fn replace_first_subslice(input: &[u8], from: &[u8], to: &[u8]) -> Option> { + let pos = input + .windows(from.len()) + .position(|candidate| candidate == from)?; + let mut out = Vec::with_capacity(input.len() - from.len() + to.len()); + out.extend_from_slice(&input[..pos]); + out.extend_from_slice(to); + out.extend_from_slice(&input[pos + from.len()..]); + Some(out) +} + +fn same_size_constructed_octet_string(primitive: &[u8], content: &[u8]) -> Vec { + assert_eq!(primitive[0], 0x04); + let header_len = tlv_header_len(primitive); + let outer_value_len = primitive.len() - header_len; + let child_len = (0..=outer_value_len) + .rev() + .find(|candidate| 1 + len_len(*candidate) + *candidate == outer_value_len) + .expect("find child length"); + let mut out = primitive[..header_len].to_vec(); + out[0] = 0x24; + out.extend(der_tlv(0x04, &content[..child_len])); + assert_eq!(out.len(), primitive.len()); + out +} + +fn tlv_header_len(tlv: &[u8]) -> usize { + if tlv[1] & 0x80 == 0 { + 2 + } else { + 2 + (tlv[1] & 0x7F) as usize + } +} + +fn len_len(len: usize) -> usize { + if len < 0x80 { + return 1; + } + let mut value = len; + let mut n = 0usize; + while value > 0 { + n += 1; + value >>= 8; + } + 1 + n +} + +fn der_tlv(tag: u8, value: &[u8]) -> Vec { + let mut out = vec![tag]; + encode_len(value.len(), &mut out); + out.extend_from_slice(value); + out +} + +fn encode_len(len: usize, out: &mut Vec) { + if len < 0x80 { + out.push(len as u8); + return; + } + let mut bytes = Vec::new(); + let mut value = len; + while value > 0 { + bytes.push((value & 0xFF) as u8); + value >>= 8; + } + bytes.reverse(); + out.push(0x80 | bytes.len() as u8); + out.extend(bytes); +} diff --git a/src/model/signed_object/types_errors.rs b/src/model/signed_object/types_errors.rs new file mode 100644 index 0000000..00c9a3b --- /dev/null +++ b/src/model/signed_object/types_errors.rs @@ -0,0 +1,300 @@ +// CMS signed-object model types and parse/validation errors. + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum EeKeyUsageSummary { + DigitalSignatureOnly, + Missing, + NotCritical, + InvalidBits, + ParseError(String), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ResourceEeCertificate { + pub raw_der: Vec, + pub subject_key_identifier: Vec, + pub spki_der: Vec, + pub rsa_public_modulus: Vec, + pub rsa_public_exponent: Vec, + pub tbs_certificate_der: Vec, + pub signature_bytes: Vec, + pub key_usage_summary: EeKeyUsageSummary, + pub sia_signed_object_uris: Vec, + pub resource_cert: ResourceCertificate, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RpkiSignedObject { + pub raw_der: Vec, + pub content_info_content_type: String, + pub signed_data: SignedDataProfiled, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SignedDataProfiled { + pub version: u32, + pub digest_algorithms: Vec, + pub encap_content_info: EncapsulatedContentInfo, + pub certificates: Vec, + pub crls_present: bool, + pub signer_infos: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct EncapsulatedContentInfo { + pub econtent_type: String, + pub econtent: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SignerInfoProfiled { + pub version: u32, + pub sid_ski: Vec, + pub digest_algorithm: String, + pub signature_algorithm: String, + pub signed_attrs: SignedAttrsProfiled, + pub unsigned_attrs_present: bool, + pub signature: Vec, + pub signed_attrs_der_for_signature: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SignedAttrsProfiled { + pub content_type: String, + pub message_digest: Vec, + pub signing_time: Asn1TimeUtc, + pub other_attrs_present: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RpkiSignedObjectParsed { + pub raw_der: Vec, + pub content_info_content_type: String, + pub signed_data: SignedDataParsed, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SignedDataParsed { + pub version: u64, + pub digest_algorithms: Vec, + pub encap_content_info: EncapsulatedContentInfoParsed, + pub certificates: Option>>, + pub crls_present: bool, + pub signer_infos: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AlgorithmIdentifierParsed { + pub oid: String, + pub params_ok: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct EncapsulatedContentInfoParsed { + pub econtent_type: String, + pub econtent: Option>, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SignerInfoParsed { + pub version: u64, + pub sid: SignerIdentifierParsed, + pub digest_algorithm: AlgorithmIdentifierParsed, + pub signature_algorithm: AlgorithmIdentifierParsed, + pub signed_attrs_content: Option>, + pub signed_attrs_der_for_signature: Option>, + pub unsigned_attrs_present: bool, + pub signature: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum SignerIdentifierParsed { + SubjectKeyIdentifier(Vec), + Other, +} + +#[derive(Debug, thiserror::Error)] +pub enum SignedObjectParseError { + #[error("DER parse error: {0} (RFC 6488 §2; RFC 6488 §3(1l); RFC 5652 §3/§5)")] + Parse(String), + + #[error("trailing bytes after DER object: {0} bytes (DER; RFC 6488 §3(1l))")] + TrailingBytes(usize), +} + +#[derive(Debug, thiserror::Error)] +pub enum SignedObjectValidateError { + #[error( + "ContentInfo.contentType must be SignedData ({OID_SIGNED_DATA}), got {0} (RFC 6488 §3(1a); RFC 5652 §3)" + )] + InvalidContentInfoContentType(String), + + #[error( + "SignedData.version must be 3, got {0} (RFC 6488 §2.1.1; RFC 6488 §3(1b); RFC 5652 §5.1)" + )] + InvalidSignedDataVersion(u64), + + #[error( + "SignedData.digestAlgorithms must contain exactly one AlgorithmIdentifier, got {0} (RFC 6488 §2.1.2; RFC 6488 §3(1b); RFC 5652 §5.1)" + )] + InvalidDigestAlgorithmsCount(usize), + + #[error( + "digest algorithm must be id-sha256 ({OID_SHA256}), got {0} (RFC 6488 §2.1.2; RFC 6488 §3(1b); RFC 7935 §2)" + )] + InvalidDigestAlgorithm(String), + + #[error("SignedData.certificates MUST be present (RFC 6488 §3(1c); RFC 5652 §5.1)")] + CertificatesMissing, + + #[error( + "SignedData.certificates must contain exactly one EE certificate, got {0} (RFC 6488 §3(1c))" + )] + InvalidCertificatesCount(usize), + + #[error("SignedData.crls MUST be omitted (RFC 6488 §3(1d))")] + CrlsPresent, + + #[error( + "SignedData.signerInfos must contain exactly one SignerInfo, got {0} (RFC 6488 §2.1; RFC 6488 §3(1e); RFC 5652 §5.1)" + )] + InvalidSignerInfosCount(usize), + + #[error("SignerInfo.version must be 3, got {0} (RFC 6488 §3(1e); RFC 5652 §5.3)")] + InvalidSignerInfoVersion(u64), + + #[error("SignerInfo.sid must be subjectKeyIdentifier [0] (RFC 6488 §3(1c); RFC 5652 §5.3)")] + InvalidSignerIdentifier, + + #[error( + "SignerInfo.digestAlgorithm must be id-sha256 ({OID_SHA256}), got {0} (RFC 6488 §3(1j); RFC 7935 §2)" + )] + InvalidSignerInfoDigestAlgorithm(String), + + #[error("SignerInfo.signedAttrs MUST be present (RFC 9589 §4; RFC 6488 §3(1f))")] + SignedAttrsMissing, + + #[error("SignerInfo.unsignedAttrs MUST be omitted (RFC 6488 §3(1i))")] + UnsignedAttrsPresent, + + #[error( + "SignerInfo.signatureAlgorithm must be rsaEncryption ({OID_RSA_ENCRYPTION}) or \ +sha256WithRSAEncryption ({OID_SHA256_WITH_RSA_ENCRYPTION}), got {0} (RFC 6488 §3(1k); RFC 7935 §2)" + )] + InvalidSignatureAlgorithm(String), + + #[error( + "SignerInfo.signatureAlgorithm parameters must be absent or NULL (RFC 5280 §4.1.1.2; RFC 7935 §2)" + )] + InvalidSignatureAlgorithmParameters, + + #[error("signedAttrs contains unsupported attribute OID {0} (RFC 9589 §4; RFC 6488 §2.1.6.4)")] + UnsupportedSignedAttribute(String), + + #[error("signedAttrs contains duplicate attribute OID {0} (RFC 6488 §2.1.6.4; RFC 9589 §4)")] + DuplicateSignedAttribute(String), + + #[error("signedAttrs parse error: {0} (RFC 5652 §5.3; RFC 6488 §3(1f); RFC 9589 §4)")] + SignedAttrsParse(String), + + #[error( + "signedAttrs attribute {oid} attrValues must contain exactly one value, got {count} (RFC 6488 §2.1.6.4; RFC 5652 §5.3)" + )] + InvalidSignedAttributeValuesCount { oid: String, count: usize }, + + #[error( + "signedAttrs missing content-type attribute (RFC 9589 §4; RFC 5652 §11.1; RFC 6488 §2.1.6.4)" + )] + SignedAttrsContentTypeMissing, + + #[error( + "signedAttrs missing message-digest attribute (RFC 9589 §4; RFC 5652 §11.2; RFC 6488 §2.1.6.4)" + )] + SignedAttrsMessageDigestMissing, + + #[error( + "signedAttrs missing signing-time attribute (RFC 9589 §4; RFC 5652 §11.3; RFC 6488 §2.1.6.4)" + )] + SignedAttrsSigningTimeMissing, + + #[error( + "signedAttrs.content-type attrValues must equal eContentType ({econtent_type}), got {attr_content_type} (RFC 6488 §3(1h); RFC 9589 §4)" + )] + ContentTypeAttrMismatch { + econtent_type: String, + attr_content_type: String, + }, + + #[error("EncapsulatedContentInfo.eContent MUST be present (RFC 6488 §2.1.3; RFC 5652 §5.2)")] + EContentMissing, + + #[error( + "signedAttrs.message-digest does not match SHA-256(eContent) (RFC 6488 §3(1f); RFC 5652 §11.2)" + )] + MessageDigestMismatch, + + #[error("EE certificate parse error: {0} (RFC 6488 §3(1c); RFC 6487 §4)")] + EeCertificateParse(String), + + #[error( + "EE certificate missing SubjectKeyIdentifier extension (RFC 6488 §3(1c); RFC 6487 §4.8.2)" + )] + EeCertificateMissingSki, + + #[error( + "EE certificate missing SubjectInfoAccess extension ({OID_SUBJECT_INFO_ACCESS}) (RFC 6487 §4.8.8.2)" + )] + EeCertificateMissingSia, + + #[error( + "EE certificate SIA missing id-ad-signedObject access method ({OID_AD_SIGNED_OBJECT}) (RFC 6487 §4.8.8.2)" + )] + EeCertificateMissingSignedObjectSia, + + #[error( + "EE certificate SIA id-ad-signedObject accessLocation must be a URI (RFC 6487 §4.8.8.2; RFC 5280 §4.2.2.2)" + )] + EeCertificateSignedObjectSiaNotUri, + + #[error( + "EE certificate SIA id-ad-signedObject must include at least one rsync:// URI (RFC 6487 §4.8.8.2)" + )] + EeCertificateSignedObjectSiaNoRsync, + + #[error( + "SignerInfo.sid SKI does not match EE certificate SKI (RFC 6488 §3(1c); RFC 5652 §5.3)" + )] + SidSkiMismatch, + + #[error( + "invalid signing-time attribute value (expected UTCTime or GeneralizedTime) (RFC 5652 §11.3; RFC 9589 §4)" + )] + InvalidSigningTimeValue, +} + +#[derive(Debug, thiserror::Error)] +pub enum SignedObjectDecodeError { + #[error("SignedObject parse error: {0}")] + Parse(#[from] SignedObjectParseError), + + #[error("SignedObject validate error: {0}")] + Validate(#[from] SignedObjectValidateError), +} + +#[derive(Debug, thiserror::Error)] +pub enum SignedObjectVerifyError { + #[error("EE SubjectPublicKeyInfo parse error: {0} (RFC 5280 §4.1.2.7)")] + EeSpkiParse(String), + + #[error("trailing bytes after EE SubjectPublicKeyInfo DER: {0} bytes (DER; RFC 5280 §4.1.2.7)")] + EeSpkiTrailingBytes(usize), + + #[error("unsupported EE public key algorithm (only RSA is supported) (RFC 7935 §2)")] + UnsupportedEePublicKeyAlgorithm, + + #[error("EE RSA public exponent invalid (RFC 8017 §A.1.1; RFC 7935 §2)")] + InvalidEeRsaExponent, + + #[error("signature verification failed (RFC 6488 §3(2)-(3); RFC 5652 §5.3; RFC 7935 §2)")] + InvalidSignature, +} diff --git a/src/model/ta.rs b/src/model/ta.rs new file mode 100644 index 0000000..5ac5013 --- /dev/null +++ b/src/model/ta.rs @@ -0,0 +1,312 @@ +use url::Url; +use x509_parser::prelude::{FromDer, X509Certificate}; + +use crate::model::oid::OID_CP_IPADDR_ASNUMBER; +use crate::model::rc::{ + AsIdentifierChoice, IpAddressChoice, ResourceCertKind, ResourceCertificate, + ResourceCertificateParseError, ResourceCertificateParsed, ResourceCertificateProfileError, + ResourceCertificateRole, +}; +use crate::model::tal::Tal; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TaCertificate { + pub raw_der: Vec, + pub rc_ca: ResourceCertificate, +} + +#[derive(Debug, thiserror::Error)] +pub enum TaCertificateParseError { + #[error("TA certificate parse error: {0} (RFC 5280 §4.1; RFC 6487 §4; RFC 8630 §2.3)")] + ResourceCertificate(#[from] ResourceCertificateParseError), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TaCertificateParsed { + pub rc_parsed: ResourceCertificateParsed, +} + +#[derive(Debug, thiserror::Error)] +pub enum TaCertificateProfileError { + #[error("resource certificate profile error: {0} (RFC 5280 §4; RFC 6487 §4)")] + ResourceCertificate(#[from] ResourceCertificateProfileError), + + #[error("TA certificate must be a CA certificate (RFC 8630 §2.3; RFC 6487 §4.8.1)")] + NotCa, + + #[error( + "TA certificate must be self-signed (issuer DN must equal subject DN) (RFC 8630 §2.3; RFC 5280 §4.1.2.4)" + )] + NotSelfSignedIssuerSubject, + + #[error( + "TA certificate must contain certificatePolicies ipAddr-asNumber ({OID_CP_IPADDR_ASNUMBER}) (RFC 6487 §4.8.9; RFC 8630 §2.3)" + )] + MissingOrInvalidCertificatePolicies, + + #[error("TA certificate must contain SubjectKeyIdentifier (RFC 6487 §4.8.2; RFC 8630 §2.3)")] + MissingSubjectKeyIdentifier, + + #[error( + "TA certificate must contain at least one RFC 3779 resource extension (IP or AS) (RFC 6487 §4.8.10-§4.8.11; RFC 8630 §2.3)" + )] + ResourcesMissing, + + #[error("TA certificate resources must be non-empty (RFC 8630 §2.3)")] + ResourcesEmpty, + + #[error( + "TA certificate MUST NOT use inherit in IP resources (RFC 8630 §2.3; RFC 3779 §2.2.3.5)" + )] + IpResourcesInherit, + + #[error( + "TA certificate MUST NOT use inherit in AS resources (RFC 8630 §2.3; RFC 3779 §3.2.3.3)" + )] + AsResourcesInherit, +} + +#[derive(Debug, thiserror::Error)] +pub enum TaCertificateDecodeError { + #[error("{0}")] + Parse(#[from] TaCertificateParseError), + + #[error("{0}")] + Validate(#[from] TaCertificateProfileError), +} + +/// Backwards-compatible name: TA certificate errors from parse+validate. +pub type TaCertificateError = TaCertificateDecodeError; + +#[derive(Debug, thiserror::Error)] +pub enum TaCertificateVerifyError { + #[error("TA certificate parse error: {0} (RFC 5280 §4.1; RFC 8630 §2.3)")] + Parse(String), + + #[error("trailing bytes after TA certificate DER: {0} bytes (DER; RFC 5280 §4.1)")] + TrailingBytes(usize), + + #[error( + "TA certificate self-signature verification failed: {0} (RFC 8630 §2.3; RFC 5280 §6.1)" + )] + InvalidSelfSignature(String), +} + +impl TaCertificate { + /// Parse step of scheme A (`parse → validate → verify`). + pub fn parse_der(der: &[u8]) -> Result { + Ok(TaCertificateParsed { + rc_parsed: ResourceCertificate::parse_der(der)?, + }) + } + + /// Profile validate step of scheme A (`parse → validate → verify`). + /// + /// `TaCertificate` is already profile-validated when constructed via `decode_der()` / + /// `TaCertificateParsed::validate_profile()`. + pub fn validate_profile(&self) -> Result<(), TaCertificateProfileError> { + Ok(()) + } + + /// Decode a TA certificate (`parse + validate`). + pub fn decode_der(der: &[u8]) -> Result { + Ok(Self::parse_der(der)?.validate_profile()?) + } + + pub fn decode_der_with_strict_name(der: &[u8]) -> Result { + let ta = Self::decode_der(der)?; + ta.rc_ca + .validate_strict_name_profile() + .map_err(TaCertificateProfileError::from)?; + Ok(ta) + } + + /// Backwards-compatible helper (historical name). + pub fn from_der(der: &[u8]) -> Result { + Self::decode_der(der) + } + + pub fn spki_der(&self) -> &[u8] { + &self.rc_ca.tbs.subject_public_key_info + } + + /// Verify step of scheme A (`parse → validate → verify`). + pub fn verify_self_signature(&self) -> Result<(), TaCertificateVerifyError> { + let (rem, cert) = X509Certificate::from_der(&self.raw_der) + .map_err(|e| TaCertificateVerifyError::Parse(e.to_string()))?; + if !rem.is_empty() { + return Err(TaCertificateVerifyError::TrailingBytes(rem.len())); + } + cert.verify_signature(None) + .map_err(|e| TaCertificateVerifyError::InvalidSelfSignature(e.to_string()))?; + Ok(()) + } + + /// Validate TA-specific semantic constraints on a parsed Resource Certificate. + /// + /// Note: this does not verify the X.509 signature; it is intended for higher-level logic and + /// for unit tests that exercise individual constraint branches. + pub fn validate_rc_constraints( + rc_ca: &ResourceCertificate, + ) -> Result<(), TaCertificateProfileError> { + if rc_ca.kind != ResourceCertKind::Ca { + return Err(TaCertificateProfileError::NotCa); + } + + if rc_ca.tbs.extensions.certificate_policies_oid.as_deref() != Some(OID_CP_IPADDR_ASNUMBER) + { + return Err(TaCertificateProfileError::MissingOrInvalidCertificatePolicies); + } + if rc_ca.tbs.extensions.subject_key_identifier.is_none() { + return Err(TaCertificateProfileError::MissingSubjectKeyIdentifier); + } + + let ip = rc_ca.tbs.extensions.ip_resources.as_ref(); + let asn = rc_ca.tbs.extensions.as_resources.as_ref(); + if ip.is_none() && asn.is_none() { + return Err(TaCertificateProfileError::ResourcesMissing); + } + + let mut has_any_resource = false; + + if let Some(ip) = ip { + if ip.has_any_inherit() { + return Err(TaCertificateProfileError::IpResourcesInherit); + } + for fam in &ip.families { + match &fam.choice { + IpAddressChoice::Inherit => { + return Err(TaCertificateProfileError::IpResourcesInherit); + } + IpAddressChoice::AddressesOrRanges(items) => { + if !items.is_empty() { + has_any_resource = true; + } + } + } + } + } + + if let Some(asn) = asn { + if matches!(asn.asnum, Some(AsIdentifierChoice::Inherit)) + || matches!(asn.rdi, Some(AsIdentifierChoice::Inherit)) + { + return Err(TaCertificateProfileError::AsResourcesInherit); + } + if let Some(AsIdentifierChoice::AsIdsOrRanges(items)) = asn.asnum.as_ref() + && !items.is_empty() + { + has_any_resource = true; + } + if let Some(AsIdentifierChoice::AsIdsOrRanges(items)) = asn.rdi.as_ref() + && !items.is_empty() + { + has_any_resource = true; + } + } + + if !has_any_resource { + return Err(TaCertificateProfileError::ResourcesEmpty); + } + + Ok(()) + } +} + +impl TaCertificateParsed { + pub fn validate_profile(self) -> Result { + let rc_ca = self.rc_parsed.validate_profile()?; + if rc_ca.kind != ResourceCertKind::Ca { + return Err(TaCertificateProfileError::NotCa); + } + rc_ca.validate_rfc6487_profile(ResourceCertificateRole::TrustAnchor)?; + + if rc_ca.tbs.issuer_name != rc_ca.tbs.subject_name { + return Err(TaCertificateProfileError::NotSelfSignedIssuerSubject); + } + + TaCertificate::validate_rc_constraints(&rc_ca)?; + + Ok(TaCertificate { + raw_der: rc_ca.raw_der.clone(), + rc_ca, + }) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TrustAnchor { + pub tal: Tal, + pub ta_certificate: TaCertificate, + pub resolved_ta_uri: Option, +} + +#[derive(Debug, thiserror::Error)] +pub enum TrustAnchorError { + #[error("TA certificate error: {0} (RFC 8630 §2.3)")] + TaCertificate(#[from] TaCertificateDecodeError), + + #[error("TA certificate self-signature error: {0} (RFC 8630 §2.3)")] + TaSelfSignature(#[from] TaCertificateVerifyError), + + #[error("{0}")] + Bind(#[from] TrustAnchorBindError), +} + +#[derive(Debug, thiserror::Error)] +pub enum TrustAnchorBindError { + #[error("resolved TA URI not listed in TAL: {0} (RFC 8630 §2.2-§2.3)")] + ResolvedUriNotInTal(String), + + #[error( + "TAL SPKI does not match TA certificate SubjectPublicKeyInfo (RFC 8630 §2.3; RFC 5280 §4.1.2.7)" + )] + TalSpkiMismatch, +} + +impl TrustAnchor { + /// Bind a TAL and a downloaded TA certificate. + /// + /// This does not download anything; it only validates the binding rules from RFC 8630 §2.3. + pub fn bind_der( + tal: Tal, + ta_der: &[u8], + resolved_uri: Option<&Url>, + ) -> Result { + let ta_certificate = TaCertificate::decode_der(ta_der)?; + ta_certificate.verify_self_signature()?; + Ok(Self::bind(tal, ta_certificate, resolved_uri)?) + } + + pub fn bind_der_with_strict_name( + tal: Tal, + ta_der: &[u8], + resolved_uri: Option<&Url>, + ) -> Result { + let ta_certificate = TaCertificate::decode_der_with_strict_name(ta_der)?; + ta_certificate.verify_self_signature()?; + Ok(Self::bind(tal, ta_certificate, resolved_uri)?) + } + + pub fn bind( + tal: Tal, + ta_certificate: TaCertificate, + resolved_uri: Option<&Url>, + ) -> Result { + if let Some(u) = resolved_uri + && !tal.ta_uris.iter().any(|x| x == u) + { + return Err(TrustAnchorBindError::ResolvedUriNotInTal(u.to_string())); + } + + if tal.subject_public_key_info_der != ta_certificate.spki_der() { + return Err(TrustAnchorBindError::TalSpkiMismatch); + } + + Ok(Self { + tal, + ta_certificate, + resolved_ta_uri: resolved_uri.cloned(), + }) + } +} diff --git a/src/model/tal.rs b/src/model/tal.rs new file mode 100644 index 0000000..53683a9 --- /dev/null +++ b/src/model/tal.rs @@ -0,0 +1,183 @@ +use base64::Engine; +use url::Url; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TalParsed { + pub raw: Vec, + /// Lines split by '\n' and normalized by stripping a trailing '\r' per line. + pub lines: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Tal { + pub raw: Vec, + pub comments: Vec, + pub ta_uris: Vec, + pub subject_public_key_info_der: Vec, +} + +#[derive(Debug, thiserror::Error)] +pub enum TalParseError { + #[error("TAL must be valid UTF-8 (RFC 8630 §2.2)")] + InvalidUtf8, +} + +#[derive(Debug, thiserror::Error)] +pub enum TalProfileError { + #[error("TAL comments must appear only at the beginning (RFC 8630 §2.2)")] + CommentAfterHeader, + + #[error("TAL must contain at least one TA URI line (RFC 8630 §2.2)")] + MissingTaUris, + + #[error( + "TAL must contain an empty line separator between URI list and SPKI base64 (RFC 8630 §2.2)" + )] + MissingSeparatorEmptyLine, + + #[error("TAL TA URI invalid: {0} (RFC 8630 §2.2)")] + InvalidUri(String), + + #[error("TAL TA URI scheme must be rsync or https, got {0} (RFC 8630 §2.2)")] + UnsupportedUriScheme(String), + + #[error( + "TAL TA URI must reference a single object (must not end with '/'): {0} (RFC 8630 §2.3)" + )] + UriIsDirectory(String), + + #[error( + "TAL must contain base64-encoded SubjectPublicKeyInfo after the separator (RFC 8630 §2.2)" + )] + MissingSpki, + + #[error("TAL SPKI base64 decode failed (RFC 8630 §2.2)")] + SpkiBase64Decode, + + #[error("TAL SPKI DER is empty (RFC 8630 §2.2)")] + SpkiDerEmpty, +} + +#[derive(Debug, thiserror::Error)] +pub enum TalDecodeError { + #[error("{0}")] + Parse(#[from] TalParseError), + + #[error("{0}")] + Validate(#[from] TalProfileError), +} + +impl Tal { + /// Parse step of scheme A (`parse → validate → verify`). + pub fn parse_bytes(input: &[u8]) -> Result { + let raw = input.to_vec(); + let text = std::str::from_utf8(input).map_err(|_| TalParseError::InvalidUtf8)?; + + let lines: Vec = text + .split('\n') + .map(|l| l.strip_suffix('\r').unwrap_or(l).to_string()) + .collect(); + + Ok(TalParsed { raw, lines }) + } + + /// Validate step of scheme A (`parse → validate → verify`). + /// + /// `Tal` is already profile-validated when constructed via `decode_bytes()` / + /// `TalParsed::validate_profile()`. + pub fn validate_profile(&self) -> Result<(), TalProfileError> { + Ok(()) + } + + pub fn decode_bytes(input: &[u8]) -> Result { + Ok(Self::parse_bytes(input)?.validate_profile()?) + } +} + +impl TalParsed { + pub fn validate_profile(self) -> Result { + let mut idx = 0usize; + + // 1) Leading comments. + let mut comments: Vec = Vec::new(); + while idx < self.lines.len() && self.lines[idx].starts_with('#') { + comments.push(self.lines[idx][1..].to_string()); + idx += 1; + } + + // 2) URI list (one or more non-empty lines). + let mut ta_uris: Vec = Vec::new(); + while idx < self.lines.len() { + let line = self.lines[idx].trim(); + if line.is_empty() { + break; + } + if line.starts_with('#') { + return Err(TalProfileError::CommentAfterHeader); + } + let url = match Url::parse(line) { + Ok(u) => u, + Err(_) => { + if !ta_uris.is_empty() { + return Err(TalProfileError::MissingSeparatorEmptyLine); + } + return Err(TalProfileError::InvalidUri(line.to_string())); + } + }; + match url.scheme() { + "rsync" | "https" => {} + s => return Err(TalProfileError::UnsupportedUriScheme(s.to_string())), + } + if url.path().ends_with('/') { + return Err(TalProfileError::UriIsDirectory(line.to_string())); + } + if url + .path_segments() + .and_then(|mut s| s.next_back()) + .unwrap_or("") + .is_empty() + { + return Err(TalProfileError::UriIsDirectory(line.to_string())); + } + ta_uris.push(url); + idx += 1; + } + + if ta_uris.is_empty() { + return Err(TalProfileError::MissingTaUris); + } + + // 3) Empty line separator (must exist). + if idx >= self.lines.len() || !self.lines[idx].trim().is_empty() { + return Err(TalProfileError::MissingSeparatorEmptyLine); + } + idx += 1; + + // 4) Base64(SPKI DER) remainder; allow line wrapping. + let mut b64 = String::new(); + while idx < self.lines.len() { + let line = self.lines[idx].trim(); + if !line.is_empty() { + b64.push_str(line); + } + idx += 1; + } + if b64.is_empty() { + return Err(TalProfileError::MissingSpki); + } + + let spki_der = base64::engine::general_purpose::STANDARD + .decode(b64.as_bytes()) + .map_err(|_| TalProfileError::SpkiBase64Decode)?; + if spki_der.is_empty() { + return Err(TalProfileError::SpkiDerEmpty); + } + + Ok(Tal { + raw: self.raw, + comments, + ta_uris, + subject_public_key_info_der: spki_der, + }) + } +} diff --git a/src/output/analysis/mod.rs b/src/output/analysis/mod.rs new file mode 100644 index 0000000..fca4906 --- /dev/null +++ b/src/output/analysis/mod.rs @@ -0,0 +1 @@ +pub mod timing; diff --git a/src/output/analysis/timing.rs b/src/output/analysis/timing.rs new file mode 100644 index 0000000..e16b86a --- /dev/null +++ b/src/output/analysis/timing.rs @@ -0,0 +1,353 @@ +use std::collections::HashMap; +use std::path::Path; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone)] +pub struct TimingHandle { + inner: Arc>, +} + +impl TimingHandle { + pub fn new(meta: TimingMeta) -> Self { + Self { + inner: Arc::new(Mutex::new(TimingCollector::new(meta))), + } + } + + pub fn span_phase(&self, phase: &'static str) -> TimingSpanGuard<'_> { + TimingSpanGuard { + handle: self.clone(), + kind: TimingSpanKind::Phase(phase), + start: Instant::now(), + } + } + + pub fn span_rrdp_repo<'a>(&self, repo_uri: &'a str) -> TimingSpanGuard<'a> { + TimingSpanGuard { + handle: self.clone(), + kind: TimingSpanKind::RrdpRepo(repo_uri), + start: Instant::now(), + } + } + + pub fn span_rrdp_repo_step<'a>( + &self, + repo_uri: &'a str, + step: &'static str, + ) -> TimingSpanGuard<'a> { + TimingSpanGuard { + handle: self.clone(), + kind: TimingSpanKind::RrdpRepoStep { repo_uri, step }, + start: Instant::now(), + } + } + + pub fn span_publication_point<'a>(&self, manifest_rsync_uri: &'a str) -> TimingSpanGuard<'a> { + TimingSpanGuard { + handle: self.clone(), + kind: TimingSpanKind::PublicationPoint(manifest_rsync_uri), + start: Instant::now(), + } + } + + pub fn set_meta(&self, update: TimingMetaUpdate<'_>) { + let mut g = self.inner.lock().expect("timing lock"); + if let Some(v) = update.tal_url { + g.meta.tal_url = Some(v.to_string()); + } + if let Some(v) = update.db_path { + g.meta.db_path = Some(v.to_string()); + } + } + + pub fn record_count(&self, key: &'static str, inc: u64) { + let mut g = self.inner.lock().expect("timing lock"); + g.counts + .entry(key) + .and_modify(|v| *v = v.saturating_add(inc)) + .or_insert(inc); + } + + pub fn counts_snapshot(&self) -> HashMap { + let g = self.inner.lock().expect("timing lock"); + g.counts + .iter() + .map(|(key, value)| ((*key).to_string(), *value)) + .collect() + } + + pub fn report_snapshot(&self, top_n: usize) -> TimingReportV1 { + let g = self.inner.lock().expect("timing lock"); + g.to_report(top_n) + } + + /// Record a phase duration directly in nanoseconds. + /// + /// This is useful when aggregating sub-phase timings locally (to reduce lock contention) + /// and then emitting a single record per publication point. + pub fn record_phase_nanos(&self, phase: &'static str, nanos: u64) { + let mut g = self.inner.lock().expect("timing lock"); + g.phases.record(phase, nanos); + } + + pub fn record_publication_point_nanos(&self, manifest_rsync_uri: &str, nanos: u64) { + let mut g = self.inner.lock().expect("timing lock"); + g.publication_points.record(manifest_rsync_uri, nanos); + } + + pub fn record_publication_point_step_nanos( + &self, + manifest_rsync_uri: &str, + step: &'static str, + nanos: u64, + ) { + let mut g = self.inner.lock().expect("timing lock"); + g.publication_point_steps + .record(&format!("{manifest_rsync_uri}::{step}"), nanos); + } + + pub fn write_json(&self, path: &Path, top_n: usize) -> Result<(), String> { + let report = { + let g = self.inner.lock().expect("timing lock"); + g.to_report(top_n) + }; + + let f = std::fs::File::create(path) + .map_err(|e| format!("create timing json failed: {}: {e}", path.display()))?; + serde_json::to_writer_pretty(f, &report) + .map_err(|e| format!("write timing json failed: {e}"))?; + Ok(()) + } + + fn record_duration(&self, kind: TimingSpanKind<'_>, duration: Duration) { + let nanos_u64 = duration.as_nanos().min(u128::from(u64::MAX)) as u64; + let mut g = self.inner.lock().expect("timing lock"); + match kind { + TimingSpanKind::Phase(name) => g.phases.record(name, nanos_u64), + TimingSpanKind::RrdpRepo(uri) => g.rrdp_repos.record(uri, nanos_u64), + TimingSpanKind::RrdpRepoStep { repo_uri, step } => g + .rrdp_repo_steps + .record(&format!("{repo_uri}::{step}"), nanos_u64), + TimingSpanKind::PublicationPoint(uri) => g.publication_points.record(uri, nanos_u64), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct TimingMeta { + pub recorded_at_utc_rfc3339: String, + pub validation_time_utc_rfc3339: String, + pub tal_url: Option, + pub db_path: Option, +} + +#[derive(Clone, Debug, Default)] +pub struct TimingMetaUpdate<'a> { + pub tal_url: Option<&'a str>, + pub db_path: Option<&'a str>, +} + +pub struct TimingSpanGuard<'a> { + handle: TimingHandle, + kind: TimingSpanKind<'a>, + start: Instant, +} + +impl Drop for TimingSpanGuard<'_> { + fn drop(&mut self) { + self.handle + .record_duration(self.kind.clone(), self.start.elapsed()); + } +} + +#[derive(Clone, Debug)] +enum TimingSpanKind<'a> { + Phase(&'static str), + RrdpRepo(&'a str), + RrdpRepoStep { + repo_uri: &'a str, + step: &'static str, + }, + PublicationPoint(&'a str), +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DurationStats { + pub count: u64, + pub total_nanos: u64, +} + +impl DurationStats { + fn record(&mut self, nanos: u64) { + self.count = self.count.saturating_add(1); + self.total_nanos = self.total_nanos.saturating_add(nanos); + } +} + +#[derive(Clone, Debug, Default)] +struct DurationStatsMap { + map: HashMap, +} + +impl DurationStatsMap { + fn record(&mut self, key: &str, nanos: u64) { + self.map.entry(key.to_string()).or_default().record(nanos); + } + + fn top(&self, n: usize) -> Vec { + let mut v = self + .map + .iter() + .map(|(k, s)| TopDurationEntry { + key: k.clone(), + count: s.count, + total_nanos: s.total_nanos, + }) + .collect::>(); + v.sort_by(|a, b| b.total_nanos.cmp(&a.total_nanos)); + v.truncate(n); + v + } +} + +struct TimingCollector { + meta: TimingMeta, + counts: HashMap<&'static str, u64>, + phases: DurationStatsMap, + rrdp_repos: DurationStatsMap, + rrdp_repo_steps: DurationStatsMap, + publication_points: DurationStatsMap, + publication_point_steps: DurationStatsMap, +} + +impl TimingCollector { + fn new(meta: TimingMeta) -> Self { + Self { + meta, + counts: HashMap::new(), + phases: DurationStatsMap::default(), + rrdp_repos: DurationStatsMap::default(), + rrdp_repo_steps: DurationStatsMap::default(), + publication_points: DurationStatsMap::default(), + publication_point_steps: DurationStatsMap::default(), + } + } + + fn to_report(&self, top_n: usize) -> TimingReportV1 { + TimingReportV1 { + format_version: 1, + meta: self.meta.clone(), + counts: self + .counts + .iter() + .map(|(k, v)| ((*k).to_string(), *v)) + .collect(), + phases: self + .phases + .map + .iter() + .map(|(k, s)| (k.clone(), s.clone())) + .collect(), + top_rrdp_repos: self.rrdp_repos.top(top_n), + top_rrdp_repo_steps: self.rrdp_repo_steps.top(top_n), + top_publication_points: self.publication_points.top(top_n), + top_publication_point_steps: self.publication_point_steps.top(top_n), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TimingReportV1 { + pub format_version: u64, + pub meta: TimingMeta, + pub counts: HashMap, + pub phases: HashMap, + pub top_rrdp_repos: Vec, + pub top_rrdp_repo_steps: Vec, + pub top_publication_points: Vec, + pub top_publication_point_steps: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct TopDurationEntry { + pub key: String, + pub count: u64, + pub total_nanos: u64, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn timing_handle_writes_json_with_phases_and_tops() { + let meta = TimingMeta { + recorded_at_utc_rfc3339: "2026-02-28T00:00:00Z".to_string(), + validation_time_utc_rfc3339: "2026-02-28T00:00:00Z".to_string(), + tal_url: Some("https://example.test/x.tal".to_string()), + db_path: Some("db".to_string()), + }; + let h = TimingHandle::new(meta); + + { + let _p = h.span_phase("tal_bootstrap"); + } + { + let _r = h.span_rrdp_repo("https://rrdp.example.test/notification.xml"); + } + { + let _s = h.span_rrdp_repo_step( + "https://rrdp.example.test/notification.xml", + "fetch_notification", + ); + } + { + let _pp = h.span_publication_point("rsync://example.test/repo/manifest.mft"); + } + h.record_count("vrps", 42); + h.record_publication_point_nanos("rsync://example.test/repo/manifest.mft", 1_000_000); + h.record_publication_point_step_nanos( + "rsync://example.test/repo/manifest.mft", + "fresh_snapshot_prepare", + 1_000_000, + ); + + let dir = tempfile::tempdir().expect("tempdir"); + let path = dir.path().join("timing.json"); + h.write_json(&path, 10).expect("write_json"); + + let rep: TimingReportV1 = + serde_json::from_slice(&std::fs::read(&path).expect("read timing.json")) + .expect("parse timing.json"); + assert_eq!(rep.format_version, 1); + assert!(rep.phases.contains_key("tal_bootstrap")); + assert_eq!(rep.counts.get("vrps").copied(), Some(42)); + assert!( + rep.top_rrdp_repos + .iter() + .any(|e| e.key.contains("rrdp.example.test")), + "expected repo in top list" + ); + assert!( + rep.top_rrdp_repo_steps + .iter() + .any(|e| e.key.contains("fetch_notification")), + "expected repo step in top list" + ); + assert!( + rep.top_publication_points + .iter() + .any(|e| e.key.contains("manifest.mft")), + "expected PP in top list" + ); + assert!( + rep.top_publication_point_steps + .iter() + .any(|e| e.key.contains("fresh_snapshot_prepare")), + "expected PP step in top list" + ); + } +} diff --git a/src/output/audit.rs b/src/output/audit.rs new file mode 100644 index 0000000..6c62e64 --- /dev/null +++ b/src/output/audit.rs @@ -0,0 +1,336 @@ +use serde::Serialize; +use sha2::Digest; + +use crate::validation::policy::Policy; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum AuditObjectKind { + Manifest, + Crl, + Certificate, + RouterCertificate, + Roa, + Aspa, + Other, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum AuditObjectResult { + Ok, + Skipped, + Error, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct ObjectAuditEntry { + pub rsync_uri: String, + pub sha256_hex: String, + pub kind: AuditObjectKind, + pub result: AuditObjectResult, + #[serde(skip_serializing_if = "Option::is_none")] + pub detail: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct AuditWarning { + pub message: String, + pub category: String, + pub rfc_refs: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub context: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct QueryAuditManifest { + pub schema_version: u32, + pub status: String, + pub events_path: String, + pub events_count: u64, + pub events_sha256: String, + pub writer_version: u32, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ValidationEventCounts { + #[serde(skip_serializing_if = "Option::is_none")] + pub objects: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub warnings: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub vrps: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub aspas: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ValidationEvent { + pub schema_version: u32, + pub seq: u64, + pub event_type: String, + pub validation_time: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub pp_node_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub pp_manifest_uri: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub pp_rsync_base_uri: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub repo_sync_phase: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub repo_terminal_state: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub object_uri: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub sha256: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub object_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub result: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub counts: Option, +} + +impl From<&crate::output::report::Warning> for AuditWarning { + fn from(w: &crate::output::report::Warning) -> Self { + Self { + message: w.message.clone(), + category: w.category.as_str().to_string(), + rfc_refs: w.rfc_refs.iter().map(|r| r.0.to_string()).collect(), + context: w.context.clone(), + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] +pub struct PublicationPointAudit { + /// Monotonic node ID assigned by the traversal engine. + /// + /// Present when running via the Stage2 tree engine; may be absent in ad-hoc runs. + #[serde(skip_serializing_if = "Option::is_none")] + pub node_id: Option, + /// Parent node ID in the traversal tree. + #[serde(skip_serializing_if = "Option::is_none")] + pub parent_node_id: Option, + /// Provenance metadata for non-root nodes (how this CA instance was discovered). + #[serde(skip_serializing_if = "Option::is_none")] + pub discovered_from: Option, + + pub rsync_base_uri: String, + pub manifest_rsync_uri: String, + pub publication_point_rsync_uri: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub rrdp_notification_uri: Option, + + pub source: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub repo_sync_source: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub repo_sync_phase: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub repo_sync_duration_ms: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub repo_sync_error: Option, + pub repo_terminal_state: String, + pub this_update_rfc3339_utc: String, + pub next_update_rfc3339_utc: String, + pub verified_at_rfc3339_utc: String, + + pub warnings: Vec, + pub objects: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct DiscoveredFrom { + pub parent_manifest_rsync_uri: String, + pub child_ca_certificate_rsync_uri: String, + pub child_ca_certificate_sha256_hex: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct TreeSummary { + pub instances_processed: usize, + pub instances_failed: usize, + pub warnings: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct AuditRunMeta { + pub validation_time_rfc3339_utc: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum AuditDownloadKind { + RrdpNotification, + RrdpSnapshot, + RrdpDelta, + Rsync, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] +pub struct AuditDownloadObjectsStat { + pub objects_count: u64, + pub objects_bytes_total: u64, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct AuditDownloadEvent { + pub kind: AuditDownloadKind, + pub uri: String, + pub started_at_rfc3339_utc: String, + pub finished_at_rfc3339_utc: String, + pub duration_ms: u64, + pub success: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub bytes: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub objects: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] +pub struct AuditDownloadKindStats { + pub ok_total: u64, + pub fail_total: u64, + pub duration_ms_total: u64, + #[serde(skip_serializing_if = "Option::is_none")] + pub bytes_total: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub objects_count_total: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub objects_bytes_total: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] +pub struct AuditDownloadStats { + pub events_total: u64, + /// Statistics keyed by serialized `AuditDownloadKind` string (e.g. "rrdp_snapshot"). + pub by_kind: std::collections::BTreeMap, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn publication_point_audit_serializes_object_audit_only() { + let audit = PublicationPointAudit { + objects: vec![ObjectAuditEntry { + rsync_uri: "rsync://example.test/repo/fresh.roa".to_string(), + sha256_hex: "11".repeat(32), + kind: AuditObjectKind::Roa, + result: AuditObjectResult::Ok, + detail: None, + }], + ..PublicationPointAudit::default() + }; + + let value = serde_json::to_value(&audit).expect("serialize audit"); + assert!(value.get("objects").is_some()); + } + + #[test] + fn warning_audit_keeps_the_warning_category() { + let warning = crate::output::report::Warning::new("BER-compatible CMS accepted") + .with_category(crate::output::report::WarningCategory::BerCompatibleCmsEncoding); + + let audit_warning = AuditWarning::from(&warning); + assert_eq!(audit_warning.category, "ber_compatible_cms_encoding"); + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] +pub struct AuditRepoSyncStateStat { + pub count: u64, + pub duration_ms_total: u64, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] +pub struct AuditRepoSyncStats { + pub publication_points_total: u64, + pub by_phase: std::collections::BTreeMap, + pub by_terminal_state: std::collections::BTreeMap, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct AuditReportV1 { + pub format_version: u32, + pub meta: AuditRunMeta, + pub policy: Policy, + pub tree: TreeSummary, + pub publication_points: Vec, + + pub vrps: Vec, + pub aspas: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct AuditReportV2 { + pub format_version: u32, + pub meta: AuditRunMeta, + pub policy: Policy, + pub tree: TreeSummary, + pub publication_points: Vec, + + pub vrps: Vec, + pub aspas: Vec, + + pub downloads: Vec, + pub download_stats: AuditDownloadStats, + pub repo_sync_stats: AuditRepoSyncStats, + #[serde(rename = "queryAudit", skip_serializing_if = "Option::is_none")] + pub query_audit: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct VrpOutput { + pub asn: u32, + pub prefix: String, + pub max_length: u16, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct AspaOutput { + pub customer_as_id: u32, + pub provider_as_ids: Vec, +} + +pub fn sha256_hex_from_32(bytes: &[u8; 32]) -> String { + hex::encode(bytes) +} + +pub fn sha256_hex(bytes: &[u8]) -> String { + let digest = sha2::Sha256::digest(bytes); + hex::encode(digest) +} + +pub fn format_roa_ip_prefix(p: &crate::model::roa::IpPrefix) -> String { + let addr = p.addr_bytes(); + match p.afi { + crate::model::roa::RoaAfi::Ipv4 => { + format!( + "{}.{}.{}.{}/{}", + addr[0], addr[1], addr[2], addr[3], p.prefix_len + ) + } + crate::model::roa::RoaAfi::Ipv6 => { + let mut parts = Vec::with_capacity(8); + for i in 0..8 { + let hi = addr[i * 2] as u16; + let lo = addr[i * 2 + 1] as u16; + parts.push(format!("{:x}", (hi << 8) | lo)); + } + format!("{}/{}", parts.join(":"), p.prefix_len) + } + } +} diff --git a/src/output/audit_downloads.rs b/src/output/audit_downloads.rs new file mode 100644 index 0000000..4282c12 --- /dev/null +++ b/src/output/audit_downloads.rs @@ -0,0 +1,170 @@ +use std::collections::BTreeMap; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; + +use crate::output::audit::{ + AuditDownloadEvent, AuditDownloadKind, AuditDownloadKindStats, AuditDownloadObjectsStat, + AuditDownloadStats, +}; + +#[derive(Clone, Debug, Default)] +pub struct DownloadLogHandle { + inner: Arc>>, +} + +impl DownloadLogHandle { + pub fn new() -> Self { + Self::default() + } + + pub fn record_event(&self, event: AuditDownloadEvent) { + self.inner.lock().expect("download log lock").push(event); + } + + pub fn snapshot_events(&self) -> Vec { + self.inner.lock().expect("download log lock").clone() + } + + pub fn stats_from_events(events: &[AuditDownloadEvent]) -> AuditDownloadStats { + let mut out = AuditDownloadStats { + events_total: events.len() as u64, + by_kind: BTreeMap::new(), + }; + for e in events { + let kind_key = match e.kind { + AuditDownloadKind::RrdpNotification => "rrdp_notification", + AuditDownloadKind::RrdpSnapshot => "rrdp_snapshot", + AuditDownloadKind::RrdpDelta => "rrdp_delta", + AuditDownloadKind::Rsync => "rsync", + } + .to_string(); + + let st = out + .by_kind + .entry(kind_key) + .or_insert_with(|| AuditDownloadKindStats { + ok_total: 0, + fail_total: 0, + duration_ms_total: 0, + bytes_total: None, + objects_count_total: None, + objects_bytes_total: None, + }); + if e.success { + st.ok_total = st.ok_total.saturating_add(1); + } else { + st.fail_total = st.fail_total.saturating_add(1); + } + st.duration_ms_total = st.duration_ms_total.saturating_add(e.duration_ms); + if let Some(b) = e.bytes { + st.bytes_total = Some(st.bytes_total.unwrap_or(0).saturating_add(b)); + } + if let Some(objects) = &e.objects { + st.objects_count_total = Some( + st.objects_count_total + .unwrap_or(0) + .saturating_add(objects.objects_count), + ); + st.objects_bytes_total = Some( + st.objects_bytes_total + .unwrap_or(0) + .saturating_add(objects.objects_bytes_total), + ); + } + } + out + } + + pub fn stats(&self) -> AuditDownloadStats { + let events = self.snapshot_events(); + Self::stats_from_events(&events) + } + + pub fn span_download<'a>( + &'a self, + kind: AuditDownloadKind, + uri: &'a str, + ) -> DownloadSpanGuard<'a> { + DownloadSpanGuard { + handle: self, + kind, + uri, + start_instant: Instant::now(), + started_at: time::OffsetDateTime::now_utc(), + bytes: None, + objects: None, + error: None, + success: None, + } + } +} + +pub struct DownloadSpanGuard<'a> { + handle: &'a DownloadLogHandle, + kind: AuditDownloadKind, + uri: &'a str, + start_instant: Instant, + started_at: time::OffsetDateTime, + bytes: Option, + objects: Option, + error: Option, + success: Option, +} + +impl DownloadSpanGuard<'_> { + pub fn set_bytes(&mut self, bytes: u64) { + self.bytes = Some(bytes); + } + + pub fn set_objects(&mut self, objects_count: u64, objects_bytes_total: u64) { + self.objects = Some(AuditDownloadObjectsStat { + objects_count, + objects_bytes_total, + }); + } + + pub fn set_ok(&mut self) { + self.success = Some(true); + } + + pub fn set_err(&mut self, msg: impl Into) { + self.success = Some(false); + self.error = Some(msg.into()); + } +} + +impl Drop for DownloadSpanGuard<'_> { + fn drop(&mut self) { + use time::format_description::well_known::Rfc3339; + let finished_at = time::OffsetDateTime::now_utc(); + let dur = self.start_instant.elapsed(); + let duration_ms = duration_to_ms(dur); + let started_at_rfc3339_utc = self + .started_at + .to_offset(time::UtcOffset::UTC) + .format(&Rfc3339) + .unwrap_or_else(|_| "".to_string()); + let finished_at_rfc3339_utc = finished_at + .to_offset(time::UtcOffset::UTC) + .format(&Rfc3339) + .unwrap_or_else(|_| "".to_string()); + let success = self.success.unwrap_or(false); + let event = AuditDownloadEvent { + kind: self.kind.clone(), + uri: self.uri.to_string(), + started_at_rfc3339_utc, + finished_at_rfc3339_utc, + duration_ms, + success, + error: if success { None } else { self.error.clone() }, + bytes: self.bytes, + objects: self.objects.clone(), + }; + self.handle.record_event(event); + } +} + +fn duration_to_ms(d: Duration) -> u64 { + let ms = d.as_millis(); + ms.min(u128::from(u64::MAX)) as u64 +} diff --git a/src/output/memory.rs b/src/output/memory.rs new file mode 100644 index 0000000..c162746 --- /dev/null +++ b/src/output/memory.rs @@ -0,0 +1,350 @@ +use serde::Serialize; + +use crate::repository::storage::RocksDbMemorySnapshot; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct MallocTrimProbe { + pub supported: bool, + pub return_value: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct ProcessMemorySnapshot { + pub label: String, + pub vm_rss_kb: Option, + pub vm_size_kb: Option, + pub vm_data_kb: Option, + pub vm_swap_kb: Option, + pub rss_anon_kb: Option, + pub rss_file_kb: Option, + pub rss_shmem_kb: Option, + pub threads: Option, + pub fd_count: Option, + pub smaps_rollup: Option, + pub smaps_mapping_summary: Option, + pub errors: Vec, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] +pub struct SmapsRollupSnapshot { + pub rss_kb: Option, + pub pss_kb: Option, + pub shared_clean_kb: Option, + pub shared_dirty_kb: Option, + pub private_clean_kb: Option, + pub private_dirty_kb: Option, + pub anonymous_kb: Option, + pub swap_kb: Option, + pub swap_pss_kb: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] +pub struct SmapsMappingSummary { + pub heap: SmapsMappingCategory, + pub anonymous_mmap: SmapsMappingCategory, + pub file_backed: SmapsMappingCategory, + pub stack: SmapsMappingCategory, + pub special: SmapsMappingCategory, + pub total: SmapsMappingCategory, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] +pub struct SmapsMappingCategory { + pub mappings: u64, + pub size_kb: u64, + pub rss_kb: u64, + pub pss_kb: u64, + pub private_clean_kb: u64, + pub private_dirty_kb: u64, + pub anonymous_kb: u64, + pub largest_mapping_rss_kb: u64, + pub large_mapping_count_64m: u64, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct MemoryTelemetryCheckpoint { + pub label: String, + pub elapsed_ms: u64, + pub process: ProcessMemorySnapshot, + pub rocksdb: RocksDbMemorySnapshot, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] +pub struct MemoryTelemetrySummary { + pub checkpoints: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub object_graph: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub malloc_trim_probes: Vec, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] +pub struct ObjectGraphMemorySummary { + pub captured_at_label: String, + pub total_estimated_bytes: u64, + pub sections: Vec, + pub notes: Vec, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] +pub struct ObjectGraphMemorySection { + pub name: String, + pub item_count: u64, + pub shallow_bytes: u64, + pub heap_bytes: u64, + pub estimated_bytes: u64, + pub string_count: u64, + pub string_bytes: u64, + pub string_capacity_bytes: u64, + pub vec_count: u64, + pub vec_heap_bytes: u64, + pub vec_capacity_bytes: u64, + pub details: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct ObjectGraphMemoryMetric { + pub name: String, + pub value: u64, +} + +pub fn process_memory_snapshot(label: impl Into) -> ProcessMemorySnapshot { + let label = label.into(); + let mut snapshot = ProcessMemorySnapshot { + label, + vm_rss_kb: None, + vm_size_kb: None, + vm_data_kb: None, + vm_swap_kb: None, + rss_anon_kb: None, + rss_file_kb: None, + rss_shmem_kb: None, + threads: None, + fd_count: current_fd_count(), + smaps_rollup: None, + smaps_mapping_summary: None, + errors: Vec::new(), + }; + + match std::fs::read_to_string("/proc/self/status") { + Ok(status) => parse_status(&status, &mut snapshot), + Err(err) => snapshot + .errors + .push(format!("read /proc/self/status failed: {err}")), + } + + match std::fs::read_to_string("/proc/self/smaps_rollup") { + Ok(smaps) => snapshot.smaps_rollup = Some(parse_smaps_rollup(&smaps)), + Err(err) => snapshot + .errors + .push(format!("read /proc/self/smaps_rollup failed: {err}")), + } + + match std::fs::read_to_string("/proc/self/smaps") { + Ok(smaps) => snapshot.smaps_mapping_summary = Some(parse_smaps_mapping_summary(&smaps)), + Err(err) => snapshot + .errors + .push(format!("read /proc/self/smaps failed: {err}")), + } + + snapshot +} + +#[allow(unsafe_code)] +pub fn malloc_trim_probe() -> MallocTrimProbe { + #[cfg(all(target_os = "linux", target_env = "gnu"))] + { + MallocTrimProbe { + supported: true, + return_value: Some(unsafe { malloc_trim(0) }), + } + } + #[cfg(not(all(target_os = "linux", target_env = "gnu")))] + { + MallocTrimProbe { + supported: false, + return_value: None, + } + } +} + +#[cfg(all(target_os = "linux", target_env = "gnu"))] +#[allow(unsafe_code)] +unsafe extern "C" { + fn malloc_trim(pad: usize) -> i32; +} + +fn current_fd_count() -> Option { + std::fs::read_dir("/proc/self/fd") + .ok() + .map(|entries| entries.filter_map(Result::ok).count() as u64) +} + +fn parse_status(status: &str, snapshot: &mut ProcessMemorySnapshot) { + for line in status.lines() { + let Some((key, value)) = line.split_once(':') else { + continue; + }; + let parsed = parse_kb_or_plain_u64(value); + match key { + "VmRSS" => snapshot.vm_rss_kb = parsed, + "VmSize" => snapshot.vm_size_kb = parsed, + "VmData" => snapshot.vm_data_kb = parsed, + "VmSwap" => snapshot.vm_swap_kb = parsed, + "RssAnon" => snapshot.rss_anon_kb = parsed, + "RssFile" => snapshot.rss_file_kb = parsed, + "RssShmem" => snapshot.rss_shmem_kb = parsed, + "Threads" => snapshot.threads = parsed, + _ => {} + } + } +} + +fn parse_smaps_rollup(smaps: &str) -> SmapsRollupSnapshot { + let mut snapshot = SmapsRollupSnapshot::default(); + for line in smaps.lines() { + let Some((key, value)) = line.split_once(':') else { + continue; + }; + let parsed = parse_kb_or_plain_u64(value); + match key { + "Rss" => snapshot.rss_kb = parsed, + "Pss" => snapshot.pss_kb = parsed, + "Shared_Clean" => snapshot.shared_clean_kb = parsed, + "Shared_Dirty" => snapshot.shared_dirty_kb = parsed, + "Private_Clean" => snapshot.private_clean_kb = parsed, + "Private_Dirty" => snapshot.private_dirty_kb = parsed, + "Anonymous" => snapshot.anonymous_kb = parsed, + "Swap" => snapshot.swap_kb = parsed, + "SwapPss" => snapshot.swap_pss_kb = parsed, + _ => {} + } + } + snapshot +} + +fn parse_smaps_mapping_summary(smaps: &str) -> SmapsMappingSummary { + let mut summary = SmapsMappingSummary::default(); + let mut current_path = String::new(); + let mut current = SmapsMappingCategory::default(); + let mut have_mapping = false; + + for line in smaps.lines() { + if is_smaps_mapping_header(line) { + if have_mapping { + add_mapping(&mut summary, ¤t_path, ¤t); + } + current_path = smaps_header_path(line); + current = SmapsMappingCategory { + mappings: 1, + ..SmapsMappingCategory::default() + }; + have_mapping = true; + continue; + } + + if !have_mapping { + continue; + } + + let Some((key, value)) = line.split_once(':') else { + continue; + }; + let parsed = parse_kb_or_plain_u64(value).unwrap_or(0); + match key { + "Size" => current.size_kb = parsed, + "Rss" => current.rss_kb = parsed, + "Pss" => current.pss_kb = parsed, + "Private_Clean" => current.private_clean_kb = parsed, + "Private_Dirty" => current.private_dirty_kb = parsed, + "Anonymous" => current.anonymous_kb = parsed, + _ => {} + } + } + + if have_mapping { + add_mapping(&mut summary, ¤t_path, ¤t); + } + + summary +} + +fn is_smaps_mapping_header(line: &str) -> bool { + let mut parts = line.split_whitespace(); + let Some(range) = parts.next() else { + return false; + }; + let Some(perms) = parts.next() else { + return false; + }; + let Some((start, end)) = range.split_once('-') else { + return false; + }; + !start.is_empty() + && !end.is_empty() + && start.as_bytes().iter().all(u8::is_ascii_hexdigit) + && end.as_bytes().iter().all(u8::is_ascii_hexdigit) + && perms.len() == 4 + && perms + .as_bytes() + .iter() + .all(|b| matches!(b, b'r' | b'w' | b'x' | b's' | b'p' | b'-')) +} + +fn smaps_header_path(line: &str) -> String { + line.split_whitespace() + .skip(5) + .collect::>() + .join(" ") +} + +fn add_mapping(summary: &mut SmapsMappingSummary, path: &str, mapping: &SmapsMappingCategory) { + add_category(&mut summary.total, mapping); + match mapping_category(path) { + MappingCategory::Heap => add_category(&mut summary.heap, mapping), + MappingCategory::AnonymousMmap => add_category(&mut summary.anonymous_mmap, mapping), + MappingCategory::FileBacked => add_category(&mut summary.file_backed, mapping), + MappingCategory::Stack => add_category(&mut summary.stack, mapping), + MappingCategory::Special => add_category(&mut summary.special, mapping), + } +} + +fn add_category(target: &mut SmapsMappingCategory, source: &SmapsMappingCategory) { + target.mappings += source.mappings; + target.size_kb += source.size_kb; + target.rss_kb += source.rss_kb; + target.pss_kb += source.pss_kb; + target.private_clean_kb += source.private_clean_kb; + target.private_dirty_kb += source.private_dirty_kb; + target.anonymous_kb += source.anonymous_kb; + target.largest_mapping_rss_kb = target.largest_mapping_rss_kb.max(source.rss_kb); + if source.rss_kb >= 64 * 1024 { + target.large_mapping_count_64m += source.mappings; + } +} + +enum MappingCategory { + Heap, + AnonymousMmap, + FileBacked, + Stack, + Special, +} + +fn mapping_category(path: &str) -> MappingCategory { + if path == "[heap]" { + MappingCategory::Heap + } else if path.starts_with("[stack") { + MappingCategory::Stack + } else if path.is_empty() { + MappingCategory::AnonymousMmap + } else if path.starts_with('/') { + MappingCategory::FileBacked + } else { + MappingCategory::Special + } +} + +fn parse_kb_or_plain_u64(value: &str) -> Option { + value.split_whitespace().next()?.parse::().ok() +} diff --git a/src/output/mod.rs b/src/output/mod.rs new file mode 100644 index 0000000..e3c3391 --- /dev/null +++ b/src/output/mod.rs @@ -0,0 +1,6 @@ +//! Validation artifacts and bounded runtime measurements. +pub mod analysis; +pub mod audit; +pub mod audit_downloads; +pub mod memory; +pub mod report; diff --git a/src/output/report.rs b/src/output/report.rs new file mode 100644 index 0000000..942511d --- /dev/null +++ b/src/output/report.rs @@ -0,0 +1,57 @@ +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct RfcRef(pub &'static str); + +/// Stable categories for warnings written to audit reports and exported metrics. +/// +/// New warning sites should use a specific category when one is available. The +/// default preserves the behaviour and schema of existing callers while making +/// their metric label explicit. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum WarningCategory { + #[default] + Unclassified, + BerCompatibleCmsEncoding, +} + +impl WarningCategory { + pub const fn as_str(self) -> &'static str { + match self { + Self::Unclassified => "unclassified", + Self::BerCompatibleCmsEncoding => "ber_compatible_cms_encoding", + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Warning { + pub message: String, + pub category: WarningCategory, + pub rfc_refs: Vec, + pub context: Option, +} + +impl Warning { + pub fn new(message: impl Into) -> Self { + Self { + message: message.into(), + category: WarningCategory::Unclassified, + rfc_refs: Vec::new(), + context: None, + } + } + + pub fn with_category(mut self, category: WarningCategory) -> Self { + self.category = category; + self + } + + pub fn with_rfc_refs(mut self, refs: &[RfcRef]) -> Self { + self.rfc_refs.extend_from_slice(refs); + self + } + + pub fn with_context(mut self, context: impl Into) -> Self { + self.context = Some(context.into()); + self + } +} diff --git a/src/repository/blob_store.rs b/src/repository/blob_store.rs new file mode 100644 index 0000000..08057a8 --- /dev/null +++ b/src/repository/blob_store.rs @@ -0,0 +1,835 @@ +use std::path::PathBuf; +use std::sync::Arc; + +use rocksdb::{DB, Options, WriteBatch}; + +use crate::repository::storage::{ + RawByHashEntry, RocksDbMemoryDbSnapshot, RocksStore, StorageError, StorageResult, + memory_db_snapshot_for_column_families, +}; + +const RAW_BY_HASH_KEY_PREFIX: &str = "rawbyhash:"; +const RAW_BLOB_KEY_PREFIX: &str = "rawblob:"; +const REPO_BYTES_KEY_PREFIX: &str = "sha256:"; + +fn raw_by_hash_key(sha256_hex: &str) -> String { + format!("{RAW_BY_HASH_KEY_PREFIX}{sha256_hex}") +} + +fn raw_blob_key(sha256_hex: &str) -> String { + format!("{RAW_BLOB_KEY_PREFIX}{sha256_hex}") +} + +fn repo_bytes_key(sha256_hex: &str) -> String { + format!("{REPO_BYTES_KEY_PREFIX}{sha256_hex}") +} + +fn validate_blob_sha256_hex(sha256_hex: &str) -> StorageResult<()> { + if sha256_hex.len() != 64 || !sha256_hex.as_bytes().iter().all(u8::is_ascii_hexdigit) { + return Err(StorageError::InvalidData { + entity: "raw_blob", + detail: format!("invalid sha256 hex: {sha256_hex}"), + }); + } + Ok(()) +} + +fn validate_blob_bytes(bytes: &[u8]) -> StorageResult<()> { + if bytes.is_empty() { + return Err(StorageError::InvalidData { + entity: "raw_blob", + detail: "bytes must not be empty".to_string(), + }); + } + Ok(()) +} + +pub trait RawObjectStore { + fn get_raw_entry(&self, sha256_hex: &str) -> StorageResult>; + + fn get_raw_entries_batch( + &self, + sha256_hexes: &[String], + ) -> StorageResult>>; + + fn get_blob_bytes(&self, sha256_hex: &str) -> StorageResult>> { + self.get_raw_entry(sha256_hex) + .map(|entry| entry.map(|entry| entry.bytes)) + } + + fn get_blob_bytes_batch(&self, sha256_hexes: &[String]) -> StorageResult>>> { + self.get_raw_entries_batch(sha256_hexes).map(|entries| { + entries + .into_iter() + .map(|entry| entry.map(|entry| entry.bytes)) + .collect() + }) + } +} + +#[derive(Clone, Debug)] +pub struct ExternalRawStoreDb { + path: PathBuf, + db: Arc, +} + +#[derive(Clone, Debug)] +pub struct ExternalRepoBytesDb { + path: PathBuf, + db: Arc, + read_only: bool, + secondary: bool, +} + +impl ExternalRawStoreDb { + pub fn open(path: impl Into) -> StorageResult { + let path = path.into(); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent).map_err(|e| StorageError::RocksDb(e.to_string()))?; + } + let mut opts = Options::default(); + opts.create_if_missing(true); + opts.set_compression_type(rocksdb::DBCompressionType::Lz4); + opts.set_max_open_files(512); + let db = DB::open(&opts, &path).map_err(|e| StorageError::RocksDb(e.to_string()))?; + Ok(Self { + path, + db: Arc::new(db), + }) + } + + pub fn put_raw_entry(&self, entry: &RawByHashEntry) -> StorageResult<()> { + entry.validate_internal()?; + let key = raw_by_hash_key(&entry.sha256_hex); + let blob_key = raw_blob_key(&entry.sha256_hex); + let value = serde_cbor::to_vec(entry).map_err(|e| StorageError::Codec { + entity: "raw_by_hash", + detail: e.to_string(), + })?; + let blob_value = entry.bytes.clone(); + self.db + .write({ + let mut batch = WriteBatch::default(); + batch.put(key.as_bytes(), value); + batch.put(blob_key.as_bytes(), blob_value); + batch + }) + .map_err(|e| StorageError::RocksDb(e.to_string()))?; + Ok(()) + } + + pub fn put_raw_entries_batch(&self, entries: &[RawByHashEntry]) -> StorageResult<()> { + if entries.is_empty() { + return Ok(()); + } + let mut batch = WriteBatch::default(); + for entry in entries { + entry.validate_internal()?; + let key = raw_by_hash_key(&entry.sha256_hex); + let blob_key = raw_blob_key(&entry.sha256_hex); + let value = serde_cbor::to_vec(entry).map_err(|e| StorageError::Codec { + entity: "raw_by_hash", + detail: e.to_string(), + })?; + batch.put(key.as_bytes(), value); + batch.put(blob_key.as_bytes(), entry.bytes.as_slice()); + } + self.db + .write(batch) + .map_err(|e| StorageError::RocksDb(e.to_string()))?; + Ok(()) + } + + pub fn put_blob_bytes_batch(&self, blobs: &[(String, Vec)]) -> StorageResult<()> { + if blobs.is_empty() { + return Ok(()); + } + let mut batch = WriteBatch::default(); + for (sha256_hex, bytes) in blobs { + validate_blob_sha256_hex(sha256_hex)?; + validate_blob_bytes(bytes)?; + let blob_key = raw_blob_key(sha256_hex); + batch.put(blob_key.as_bytes(), bytes.as_slice()); + } + self.db + .write(batch) + .map_err(|e| StorageError::RocksDb(e.to_string()))?; + Ok(()) + } + + pub fn delete_raw_entry(&self, sha256_hex: &str) -> StorageResult<()> { + let key = raw_by_hash_key(sha256_hex); + let blob_key = raw_blob_key(sha256_hex); + self.db + .write({ + let mut batch = WriteBatch::default(); + batch.delete(key.as_bytes()); + batch.delete(blob_key.as_bytes()); + batch + }) + .map_err(|e| StorageError::RocksDb(e.to_string())) + } + + pub fn path(&self) -> &PathBuf { + &self.path + } + + pub(crate) fn memory_snapshot(&self, label: impl Into) -> RocksDbMemoryDbSnapshot { + memory_db_snapshot_for_column_families(label, self.db.as_ref(), None) + } +} + +impl ExternalRepoBytesDb { + pub fn open(path: impl Into) -> StorageResult { + let path = path.into(); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent).map_err(|e| StorageError::RocksDb(e.to_string()))?; + } + let mut opts = Options::default(); + opts.create_if_missing(true); + opts.set_compression_type(rocksdb::DBCompressionType::Lz4); + opts.set_max_open_files(512); + let db = DB::open(&opts, &path).map_err(|e| StorageError::RocksDb(e.to_string()))?; + Ok(Self { + path, + db: Arc::new(db), + read_only: false, + secondary: false, + }) + } + + pub fn open_read_only(path: impl Into) -> StorageResult { + let path = path.into(); + let mut opts = Options::default(); + opts.set_compression_type(rocksdb::DBCompressionType::Lz4); + opts.set_max_open_files(512); + let db = DB::open_for_read_only(&opts, &path, false) + .map_err(|e| StorageError::RocksDb(e.to_string()))?; + Ok(Self { + path, + db: Arc::new(db), + read_only: true, + secondary: false, + }) + } + + /// Open the repo-bytes DB as a RocksDB secondary instance. + /// + /// Unlike `open_read_only` (a frozen point-in-time view), a secondary + /// instance can follow the live primary via `try_catch_up_with_primary`, + /// which is required when the soak keeps writing new object bytes while + /// the query service is running. + pub fn open_as_secondary( + path: impl Into, + secondary_path: impl Into, + ) -> StorageResult { + let path = path.into(); + let secondary_path = secondary_path.into(); + if let Some(parent) = secondary_path.parent() { + std::fs::create_dir_all(parent).map_err(|e| StorageError::RocksDb(e.to_string()))?; + } + let mut opts = Options::default(); + opts.set_compression_type(rocksdb::DBCompressionType::Lz4); + opts.set_max_open_files(512); + let db = DB::open_as_secondary(&opts, &path, &secondary_path) + .map_err(|e| StorageError::RocksDb(e.to_string()))?; + Ok(Self { + path, + db: Arc::new(db), + read_only: true, + secondary: true, + }) + } + + /// Pull the secondary view up to the primary's current state. No-op for + /// non-secondary handles. + pub fn try_catch_up_with_primary(&self) -> StorageResult<()> { + if self.secondary { + self.db + .try_catch_up_with_primary() + .map_err(|e| StorageError::RocksDb(e.to_string()))?; + } + Ok(()) + } + + pub fn put_blob_bytes_batch(&self, blobs: &[(String, Vec)]) -> StorageResult<()> { + if blobs.is_empty() { + return Ok(()); + } + if self.read_only { + return Err(StorageError::RocksDb(format!( + "repo-bytes DB is read-only: {}", + self.path.display() + ))); + } + let mut batch = WriteBatch::default(); + for (sha256_hex, bytes) in blobs { + validate_blob_sha256_hex(sha256_hex)?; + validate_blob_bytes(bytes)?; + let key = repo_bytes_key(sha256_hex); + batch.put(key.as_bytes(), bytes.as_slice()); + } + self.db + .write(batch) + .map_err(|e| StorageError::RocksDb(e.to_string()))?; + Ok(()) + } + + pub(crate) fn is_read_only(&self) -> bool { + self.read_only + } + + pub(crate) fn require_existing_blob_bytes_batch( + &self, + blobs: &[(String, Vec)], + ) -> StorageResult<()> { + if blobs.is_empty() { + return Ok(()); + } + let hashes = blobs + .iter() + .map(|(sha256_hex, _)| sha256_hex.clone()) + .collect::>(); + let existing = self.get_blob_bytes_batch(&hashes)?; + for ((sha256_hex, expected_bytes), actual_bytes) in blobs.iter().zip(existing) { + match actual_bytes { + Some(actual_bytes) if actual_bytes == *expected_bytes => {} + Some(_) => { + return Err(StorageError::InvalidData { + entity: "read_only_repo_bytes", + detail: format!("existing bytes differ for SHA-256 {sha256_hex}"), + }); + } + None => { + return Err(StorageError::InvalidData { + entity: "read_only_repo_bytes", + detail: format!("blob is missing for SHA-256 {sha256_hex}"), + }); + } + } + } + Ok(()) + } + + pub fn get_blob_bytes(&self, sha256_hex: &str) -> StorageResult>> { + validate_blob_sha256_hex(sha256_hex)?; + let key = repo_bytes_key(sha256_hex); + self.db + .get(key.as_bytes()) + .map_err(|e| StorageError::RocksDb(e.to_string())) + } + + pub fn get_blob_bytes_batch( + &self, + sha256_hexes: &[String], + ) -> StorageResult>>> { + if sha256_hexes.is_empty() { + return Ok(Vec::new()); + } + let keys: Vec = sha256_hexes + .iter() + .map(|hash| { + validate_blob_sha256_hex(hash)?; + Ok::(repo_bytes_key(hash)) + }) + .collect::>()?; + self.db + .multi_get(keys.iter().map(|key| key.as_bytes())) + .into_iter() + .map(|res| res.map_err(|e| StorageError::RocksDb(e.to_string()))) + .collect() + } + + pub fn path(&self) -> &PathBuf { + &self.path + } + + pub(crate) fn memory_snapshot(&self, label: impl Into) -> RocksDbMemoryDbSnapshot { + memory_db_snapshot_for_column_families(label, self.db.as_ref(), None) + } +} + +impl RawObjectStore for RocksStore { + fn get_raw_entry(&self, sha256_hex: &str) -> StorageResult> { + self.get_raw_by_hash_entry(sha256_hex) + } + + fn get_raw_entries_batch( + &self, + sha256_hexes: &[String], + ) -> StorageResult>> { + self.get_raw_by_hash_entries_batch(sha256_hexes) + } + + fn get_blob_bytes(&self, sha256_hex: &str) -> StorageResult>> { + RocksStore::get_blob_bytes(self, sha256_hex) + } + + fn get_blob_bytes_batch(&self, sha256_hexes: &[String]) -> StorageResult>>> { + RocksStore::get_blob_bytes_batch(self, sha256_hexes) + } +} + +impl RawObjectStore for ExternalRawStoreDb { + fn get_raw_entry(&self, sha256_hex: &str) -> StorageResult> { + let key = raw_by_hash_key(sha256_hex); + let Some(bytes) = self + .db + .get(key.as_bytes()) + .map_err(|e| StorageError::RocksDb(e.to_string()))? + else { + return Ok(None); + }; + let entry = + serde_cbor::from_slice::(&bytes).map_err(|e| StorageError::Codec { + entity: "raw_by_hash", + detail: e.to_string(), + })?; + entry.validate_internal()?; + Ok(Some(entry)) + } + + fn get_raw_entries_batch( + &self, + sha256_hexes: &[String], + ) -> StorageResult>> { + if sha256_hexes.is_empty() { + return Ok(Vec::new()); + } + let keys: Vec = sha256_hexes + .iter() + .map(|hash| raw_by_hash_key(hash)) + .collect(); + self.db + .multi_get(keys.iter().map(|key| key.as_bytes())) + .into_iter() + .map(|res| { + let maybe = res.map_err(|e| StorageError::RocksDb(e.to_string()))?; + match maybe { + Some(bytes) => { + let entry = + serde_cbor::from_slice::(&bytes).map_err(|e| { + StorageError::Codec { + entity: "raw_by_hash", + detail: e.to_string(), + } + })?; + entry.validate_internal()?; + Ok(Some(entry)) + } + None => Ok(None), + } + }) + .collect() + } + + fn get_blob_bytes(&self, sha256_hex: &str) -> StorageResult>> { + let key = raw_blob_key(sha256_hex); + self.db + .get(key.as_bytes()) + .map_err(|e| StorageError::RocksDb(e.to_string())) + } + + fn get_blob_bytes_batch(&self, sha256_hexes: &[String]) -> StorageResult>>> { + if sha256_hexes.is_empty() { + return Ok(Vec::new()); + } + let keys: Vec = sha256_hexes.iter().map(|hash| raw_blob_key(hash)).collect(); + self.db + .multi_get(keys.iter().map(|key| key.as_bytes())) + .into_iter() + .map(|res| res.map_err(|e| StorageError::RocksDb(e.to_string()))) + .collect() + } +} + +#[cfg(test)] +mod tests { + use super::{ExternalRawStoreDb, ExternalRepoBytesDb, RawObjectStore}; + use crate::repository::storage::{RawByHashEntry, RocksStore, StorageError, StorageResult}; + use std::collections::HashMap; + + fn sha256_hex(bytes: &[u8]) -> String { + use sha2::{Digest, Sha256}; + hex::encode(Sha256::digest(bytes)) + } + + #[derive(Default)] + struct MockRawStore { + entries: HashMap, + } + + impl RawObjectStore for MockRawStore { + fn get_raw_entry(&self, sha256_hex: &str) -> StorageResult> { + Ok(self.entries.get(sha256_hex).cloned()) + } + + fn get_raw_entries_batch( + &self, + sha256_hexes: &[String], + ) -> StorageResult>> { + Ok(sha256_hexes + .iter() + .map(|hash| self.entries.get(hash).cloned()) + .collect()) + } + } + + #[test] + fn rocks_store_raw_object_store_reads_single_and_batch_entries() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(td.path()).expect("open rocksdb"); + + let a = b"object-a".to_vec(); + let b = b"object-b".to_vec(); + let a_hash = sha256_hex(&a); + let b_hash = sha256_hex(&b); + + store + .put_raw_by_hash_entry(&RawByHashEntry::from_bytes(a_hash.clone(), a.clone())) + .expect("put a"); + store + .put_raw_by_hash_entry(&RawByHashEntry::from_bytes(b_hash.clone(), b.clone())) + .expect("put b"); + + let single = store + .get_raw_entry(&a_hash) + .expect("get single") + .expect("present"); + assert_eq!(single.bytes, a); + + let batch = store + .get_raw_entries_batch(&[a_hash.clone(), "00".repeat(32), b_hash.clone()]) + .expect("get batch"); + assert_eq!(batch.len(), 3); + assert_eq!( + batch[0].as_ref().map(|entry| entry.bytes.as_slice()), + Some(a.as_slice()) + ); + assert!(batch[1].is_none()); + assert_eq!( + batch[2].as_ref().map(|entry| entry.bytes.as_slice()), + Some(b.as_slice()) + ); + } + + #[test] + fn external_raw_store_db_roundtrips_entries() { + let td = tempfile::tempdir().expect("tempdir"); + let raw_store = + ExternalRawStoreDb::open(td.path().join("raw-store.db")).expect("open raw store"); + + let mut entry = RawByHashEntry::from_bytes(sha256_hex(b"blob"), b"blob".to_vec()); + entry + .origin_uris + .push("rsync://example.test/repo/a.cer".to_string()); + entry.object_type = Some("cer".to_string()); + raw_store.put_raw_entry(&entry).expect("put raw entry"); + + let got = raw_store + .get_raw_entry(&entry.sha256_hex) + .expect("read raw entry") + .expect("entry exists"); + assert_eq!(got, entry); + } + + #[test] + fn external_raw_store_db_batch_writes_and_reads() { + let td = tempfile::tempdir().expect("tempdir"); + let raw_store = + ExternalRawStoreDb::open(td.path().join("raw-store.db")).expect("open raw store"); + + let a = RawByHashEntry::from_bytes(sha256_hex(b"a"), b"a".to_vec()); + let b = RawByHashEntry::from_bytes(sha256_hex(b"b"), b"b".to_vec()); + raw_store + .put_raw_entries_batch(&[a.clone(), b.clone()]) + .expect("batch put"); + + let batch = raw_store + .get_raw_entries_batch(&[a.sha256_hex.clone(), b.sha256_hex.clone()]) + .expect("batch get"); + assert_eq!(batch.len(), 2); + assert_eq!(batch[0], Some(a)); + assert_eq!(batch[1], Some(b)); + } + + #[test] + fn raw_object_store_default_blob_helpers_return_bytes_only() { + let td = tempfile::tempdir().expect("tempdir"); + let raw_store = ExternalRawStoreDb::open(td.path().join("nested/raw-store.db")) + .expect("open raw store"); + + let mut entry = RawByHashEntry::from_bytes(sha256_hex(b"blob"), b"blob".to_vec()); + entry + .origin_uris + .push("rsync://example.test/repo/blob.roa".to_string()); + raw_store.put_raw_entry(&entry).expect("put raw entry"); + + let single = raw_store + .get_blob_bytes(&entry.sha256_hex) + .expect("get blob bytes") + .expect("entry exists"); + assert_eq!(single, b"blob".to_vec()); + + let batch = raw_store + .get_blob_bytes_batch(&[entry.sha256_hex.clone(), "00".repeat(32)]) + .expect("get blob bytes batch"); + assert_eq!(batch, vec![Some(b"blob".to_vec()), None]); + } + + #[test] + fn raw_object_store_default_blob_helpers_work_for_custom_store() { + let mut store = MockRawStore::default(); + let a = RawByHashEntry::from_bytes(sha256_hex(b"a"), b"a".to_vec()); + let b = RawByHashEntry::from_bytes(sha256_hex(b"b"), b"b".to_vec()); + store.entries.insert(a.sha256_hex.clone(), a.clone()); + store.entries.insert(b.sha256_hex.clone(), b.clone()); + + let single = store + .get_blob_bytes(&a.sha256_hex) + .expect("single blob bytes") + .expect("present"); + assert_eq!(single, b"a".to_vec()); + + let batch = store + .get_blob_bytes_batch(&[a.sha256_hex.clone(), "00".repeat(32), b.sha256_hex.clone()]) + .expect("batch blob bytes"); + assert_eq!(batch, vec![Some(b"a".to_vec()), None, Some(b"b".to_vec())]); + } + + #[test] + fn rocks_store_blob_helpers_use_external_raw_store_fast_path() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open_with_external_raw_store( + &td.path().join("db"), + &td.path().join("raw-store.db"), + ) + .expect("open store with external raw store"); + + let entry = RawByHashEntry::from_bytes(sha256_hex(b"blob-fast"), b"blob-fast".to_vec()); + store.put_raw_by_hash_entry(&entry).expect("put"); + + let single = store + .get_blob_bytes(&entry.sha256_hex) + .expect("single blob bytes") + .expect("present"); + assert_eq!(single, b"blob-fast".to_vec()); + + let batch = store + .get_blob_bytes_batch(&[entry.sha256_hex.clone(), "00".repeat(32)]) + .expect("batch blob bytes"); + assert_eq!(batch, vec![Some(b"blob-fast".to_vec()), None]); + } + + #[test] + fn external_raw_store_db_delete_removes_entry() { + let td = tempfile::tempdir().expect("tempdir"); + let raw_store = + ExternalRawStoreDb::open(td.path().join("raw-store.db")).expect("open raw store"); + + let entry = RawByHashEntry::from_bytes(sha256_hex(b"gone"), b"gone".to_vec()); + raw_store.put_raw_entry(&entry).expect("put"); + assert!( + raw_store + .get_raw_entry(&entry.sha256_hex) + .unwrap() + .is_some() + ); + + raw_store + .delete_raw_entry(&entry.sha256_hex) + .expect("delete entry"); + assert!( + raw_store + .get_raw_entry(&entry.sha256_hex) + .unwrap() + .is_none() + ); + } + + #[test] + fn put_blob_bytes_batch_round_trips_without_raw_entry() { + let td = tempfile::tempdir().expect("tempdir"); + let raw_store = + ExternalRawStoreDb::open(td.path().join("raw-store.db")).expect("open raw store"); + + let a = (sha256_hex(b"blob-a"), b"blob-a".to_vec()); + let b = (sha256_hex(b"blob-b"), b"blob-b".to_vec()); + raw_store + .put_blob_bytes_batch(&[a.clone(), b.clone()]) + .expect("put blobs"); + + assert_eq!( + raw_store.get_blob_bytes(&a.0).expect("get blob a"), + Some(a.1.clone()) + ); + assert_eq!( + raw_store.get_blob_bytes(&b.0).expect("get blob b"), + Some(b.1.clone()) + ); + assert!(raw_store.get_raw_entry(&a.0).expect("get raw a").is_none()); + assert!(raw_store.get_raw_entry(&b.0).expect("get raw b").is_none()); + } + + #[test] + fn put_blob_bytes_batch_rejects_invalid_inputs() { + let td = tempfile::tempdir().expect("tempdir"); + let raw_store = + ExternalRawStoreDb::open(td.path().join("raw-store.db")).expect("open raw store"); + + let err = raw_store + .put_blob_bytes_batch(&[("zz".repeat(32), b"blob".to_vec())]) + .expect_err("invalid hash should fail"); + assert!(matches!(err, StorageError::InvalidData { .. })); + + let err = raw_store + .put_blob_bytes_batch(&[(sha256_hex(b"blob"), Vec::new())]) + .expect_err("empty bytes should fail"); + assert!(matches!(err, StorageError::InvalidData { .. })); + } + + #[test] + fn external_raw_store_db_rejects_invalid_entry_on_put() { + let td = tempfile::tempdir().expect("tempdir"); + let raw_store = + ExternalRawStoreDb::open(td.path().join("raw-store.db")).expect("open raw store"); + + let bad = RawByHashEntry { + sha256_hex: "11".repeat(32), + bytes: b"blob".to_vec(), + origin_uris: Vec::new(), + object_type: None, + encoding: None, + }; + let err = raw_store + .put_raw_entry(&bad) + .expect_err("invalid hash should fail"); + assert!(matches!(err, StorageError::InvalidData { .. })); + } + + #[test] + fn external_raw_store_db_reports_codec_error_for_corrupt_value() { + let td = tempfile::tempdir().expect("tempdir"); + let raw_store = + ExternalRawStoreDb::open(td.path().join("raw-store.db")).expect("open raw store"); + raw_store + .db + .put(b"rawbyhash:deadbeef", b"not-cbor") + .expect("inject corrupt bytes"); + + let err = raw_store + .get_raw_entry("deadbeef") + .expect_err("corrupt value should fail"); + assert!(matches!( + err, + StorageError::Codec { + entity: "raw_by_hash", + .. + } + )); + } + + #[test] + fn external_raw_store_db_batch_returns_empty_for_empty_request() { + let td = tempfile::tempdir().expect("tempdir"); + let raw_store = + ExternalRawStoreDb::open(td.path().join("raw-store.db")).expect("open raw store"); + let entries = raw_store + .get_raw_entries_batch(&[]) + .expect("empty batch succeeds"); + assert!(entries.is_empty()); + raw_store + .put_raw_entries_batch(&[]) + .expect("empty put succeeds"); + } + + #[test] + fn external_repo_bytes_db_roundtrips_blob_bytes_without_raw_entry() { + let td = tempfile::tempdir().expect("tempdir"); + let repo_bytes = + ExternalRepoBytesDb::open(td.path().join("repo-bytes.db")).expect("open repo bytes"); + let bytes = b"repo-bytes-object".to_vec(); + let hash = sha256_hex(&bytes); + + repo_bytes + .put_blob_bytes_batch(&[(hash.clone(), bytes.clone())]) + .expect("put repo bytes"); + + assert_eq!( + repo_bytes.get_blob_bytes(&hash).expect("get repo bytes"), + Some(bytes.clone()) + ); + assert_eq!( + repo_bytes + .get_blob_bytes_batch(&[hash, "00".repeat(32)]) + .expect("get repo bytes batch"), + vec![Some(bytes), None] + ); + } + + #[test] + fn external_repo_bytes_db_rejects_invalid_inputs() { + let td = tempfile::tempdir().expect("tempdir"); + let repo_bytes = + ExternalRepoBytesDb::open(td.path().join("repo-bytes.db")).expect("open repo bytes"); + + assert!( + repo_bytes + .put_blob_bytes_batch(&[("not-a-valid-hash".to_string(), b"blob".to_vec())]) + .is_err() + ); + assert!( + repo_bytes + .put_blob_bytes_batch(&[(sha256_hex(b"blob"), Vec::new())]) + .is_err() + ); + assert!(repo_bytes.get_blob_bytes("not-a-valid-hash").is_err()); + } + + #[test] + fn external_repo_bytes_db_secondary_catches_up_with_live_primary() { + let td = tempfile::tempdir().expect("tempdir"); + let primary_path = td.path().join("repo-bytes.db"); + let secondary_path = td.path().join("repo-bytes.secondary"); + + let primary = ExternalRepoBytesDb::open(&primary_path).expect("open primary"); + let bytes_a = b"repo-bytes-a".to_vec(); + let hash_a = sha256_hex(&bytes_a); + primary + .put_blob_bytes_batch(&[(hash_a.clone(), bytes_a.clone())]) + .expect("put a"); + + let secondary = ExternalRepoBytesDb::open_as_secondary(&primary_path, &secondary_path) + .expect("open secondary"); + assert!(secondary.secondary); + + // A secondary open does not necessarily see pre-existing data until it + // catches up with the primary's manifest. + secondary + .try_catch_up_with_primary() + .expect("initial catch up"); + assert_eq!( + secondary + .get_blob_bytes(&hash_a) + .expect("get a via secondary"), + Some(bytes_a) + ); + + // Bytes written by the primary *after* the secondary open become + // visible after another catch-up (this is the live-soak scenario). + let bytes_b = b"repo-bytes-b".to_vec(); + let hash_b = sha256_hex(&bytes_b); + primary + .put_blob_bytes_batch(&[(hash_b.clone(), bytes_b.clone())]) + .expect("put b after secondary open"); + secondary + .try_catch_up_with_primary() + .expect("second catch up"); + assert_eq!( + secondary + .get_blob_bytes(&hash_b) + .expect("get b via secondary"), + Some(bytes_b) + ); + } +} diff --git a/src/repository/current_repo_index.rs b/src/repository/current_repo_index.rs new file mode 100644 index 0000000..a896520 --- /dev/null +++ b/src/repository/current_repo_index.rs @@ -0,0 +1,289 @@ +use std::collections::{HashMap, HashSet}; +use std::sync::{Arc, RwLock}; + +use crate::repository::storage::{RepositoryViewEntry, RepositoryViewState}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CurrentRepoEntry { + pub current_hash: [u8; 32], + pub current_hash_hex: String, + pub repository_source: String, + pub object_type: Option, + pub state: RepositoryViewState, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct CurrentRepoObject { + pub rsync_uri: String, + pub current_hash_hex: String, + pub repository_source: String, + pub object_type: Option, +} + +#[derive(Default, Debug)] +pub struct CurrentRepoIndex { + by_uri: HashMap, +} + +/// Shared handle to the run-wide current repository index. Readers (phase 2 +/// publication point staging, cache lookups, output snapshots) take +/// `.read()` and no longer exclude each other; writers (repo sync transports +/// applying repository view entries, run-state reset) take `.write()` and +/// stay exclusive. +pub type CurrentRepoIndexHandle = Arc>; + +impl CurrentRepoIndex { + pub fn new() -> Self { + Self::default() + } + + pub fn shared() -> CurrentRepoIndexHandle { + Arc::new(RwLock::new(Self::new())) + } + + pub fn get_by_uri(&self, rsync_uri: &str) -> Option<&CurrentRepoEntry> { + self.by_uri.get(rsync_uri) + } + + pub fn list_scope_uris(&self, repository_source: &str) -> Vec { + let mut out = self + .by_uri + .iter() + .filter(|&(_rsync_uri, entry)| entry.repository_source == repository_source) + .map(|(rsync_uri, _entry)| rsync_uri.clone()) + .collect::>(); + out.sort(); + out + } + + pub fn active_uri_count(&self) -> usize { + self.by_uri.len() + } + + pub fn scope_count(&self) -> usize { + self.by_uri + .values() + .map(|entry| entry.repository_source.as_str()) + .collect::>() + .len() + } + + pub fn snapshot_objects(&self) -> Vec { + let mut out = self + .by_uri + .iter() + .map(|(rsync_uri, entry)| CurrentRepoObject { + rsync_uri: rsync_uri.clone(), + current_hash_hex: entry.current_hash_hex.clone(), + repository_source: entry.repository_source.clone(), + object_type: entry.object_type.clone(), + }) + .collect::>(); + out.sort(); + out + } + + pub fn clear(&mut self) { + self.by_uri.clear(); + } + + pub fn apply_repository_view_entries( + &mut self, + entries: &[RepositoryViewEntry], + ) -> Result<(), String> { + for entry in entries { + self.apply_repository_view_entry(entry)?; + } + Ok(()) + } + + fn apply_repository_view_entry(&mut self, entry: &RepositoryViewEntry) -> Result<(), String> { + entry.validate_internal().map_err(|e| e.to_string())?; + + match entry.state { + RepositoryViewState::Present | RepositoryViewState::Replaced => { + let repository_source = entry.repository_source.clone().ok_or_else(|| { + format!( + "repository_view entry missing repository_source for current object {}", + entry.rsync_uri + ) + })?; + let current_hash_hex = entry.current_hash.clone().ok_or_else(|| { + format!( + "repository_view entry missing current_hash for current object {}", + entry.rsync_uri + ) + })?; + let current_hash = decode_sha256_hex_32(¤t_hash_hex)?; + self.by_uri.insert( + entry.rsync_uri.clone(), + CurrentRepoEntry { + current_hash, + current_hash_hex: current_hash_hex.to_ascii_lowercase(), + repository_source, + object_type: entry.object_type.clone(), + state: entry.state, + }, + ); + } + RepositoryViewState::Withdrawn => { + self.by_uri.remove(&entry.rsync_uri); + } + } + + Ok(()) + } +} + +fn decode_sha256_hex_32(value: &str) -> Result<[u8; 32], String> { + if value.len() != 64 || !value.as_bytes().iter().all(u8::is_ascii_hexdigit) { + return Err(format!("invalid sha256 hex: {value}")); + } + let mut out = [0u8; 32]; + hex::decode_to_slice(value, &mut out).map_err(|e| format!("hex decode failed: {e}"))?; + Ok(out) +} + +#[cfg(test)] +mod tests { + use super::CurrentRepoIndex; + use crate::repository::storage::{RepositoryViewEntry, RepositoryViewState}; + + fn present(source: &str, uri: &str, hash: &str) -> RepositoryViewEntry { + RepositoryViewEntry { + rsync_uri: uri.to_string(), + current_hash: Some(hash.to_string()), + repository_source: Some(source.to_string()), + object_type: Some("roa".to_string()), + state: RepositoryViewState::Present, + } + } + + #[test] + fn current_repo_index_tracks_present_and_withdrawn_entries() { + let mut index = CurrentRepoIndex::new(); + let uri = "rsync://example.test/repo/a.roa"; + let source = "rsync://example.test/repo/"; + let hash = &"11".repeat(32); + index + .apply_repository_view_entries(&[present(source, uri, hash)]) + .expect("apply present"); + let got = index.get_by_uri(uri).expect("current entry"); + assert_eq!(got.current_hash_hex, hash.to_string()); + assert_eq!(index.list_scope_uris(source), vec![uri.to_string()]); + + index + .apply_repository_view_entries(&[RepositoryViewEntry { + rsync_uri: uri.to_string(), + current_hash: Some(hash.to_string()), + repository_source: Some(source.to_string()), + object_type: Some("roa".to_string()), + state: RepositoryViewState::Withdrawn, + }]) + .expect("apply withdrawn"); + assert!(index.get_by_uri(uri).is_none()); + assert!(index.list_scope_uris(source).is_empty()); + } + + #[test] + fn current_repo_index_moves_uri_between_scopes() { + let mut index = CurrentRepoIndex::new(); + let uri = "rsync://example.test/repo/a.roa"; + let old_scope = "rsync://example.test/repo/"; + let new_scope = "https://rrdp.example.test/notification.xml"; + index + .apply_repository_view_entries(&[present(old_scope, uri, &"22".repeat(32))]) + .expect("apply old scope"); + index + .apply_repository_view_entries(&[present(new_scope, uri, &"33".repeat(32))]) + .expect("apply new scope"); + + assert!(index.list_scope_uris(old_scope).is_empty()); + assert_eq!(index.list_scope_uris(new_scope), vec![uri.to_string()]); + assert_eq!( + index.get_by_uri(uri).expect("entry").current_hash_hex, + "33".repeat(32) + ); + } + + #[test] + fn current_repo_index_snapshot_objects_and_counts_are_sorted() { + let handle = CurrentRepoIndex::shared(); + let mut index = handle.write().expect("write-lock index"); + index + .apply_repository_view_entries(&[ + present( + "rsync://example.test/repo-b/", + "rsync://example.test/repo-b/b.roa", + &"22".repeat(32), + ), + present( + "rsync://example.test/repo-a/", + "rsync://example.test/repo-a/a.roa", + &"11".repeat(32), + ), + ]) + .expect("apply present entries"); + assert_eq!(index.active_uri_count(), 2); + assert_eq!(index.scope_count(), 2); + + let snapshot = index.snapshot_objects(); + assert_eq!(snapshot.len(), 2); + assert_eq!(snapshot[0].rsync_uri, "rsync://example.test/repo-a/a.roa"); + assert_eq!(snapshot[1].rsync_uri, "rsync://example.test/repo-b/b.roa"); + } + + #[test] + fn current_repo_index_reports_missing_fields_and_invalid_hash() { + let mut index = CurrentRepoIndex::new(); + let err = index + .apply_repository_view_entries(&[RepositoryViewEntry { + rsync_uri: "rsync://example.test/repo/a.roa".to_string(), + current_hash: Some("11".repeat(32)), + repository_source: None, + object_type: Some("roa".to_string()), + state: RepositoryViewState::Present, + }]) + .expect_err("missing source should fail"); + assert!(err.contains("missing repository_source"), "{err}"); + + let err = index + .apply_repository_view_entries(&[RepositoryViewEntry { + rsync_uri: "rsync://example.test/repo/a.roa".to_string(), + current_hash: Some("not-a-valid-sha256".to_string()), + repository_source: Some("rsync://example.test/repo/".to_string()), + object_type: Some("roa".to_string()), + state: RepositoryViewState::Present, + }]) + .expect_err("invalid hash should fail"); + assert!(err.contains("invalid"), "{err}"); + + index + .apply_repository_view_entries(&[RepositoryViewEntry { + rsync_uri: "rsync://example.test/repo/b.roa".to_string(), + current_hash: Some("22".repeat(32)), + repository_source: Some("rsync://example.test/repo/".to_string()), + object_type: Some("roa".to_string()), + state: RepositoryViewState::Present, + }]) + .expect("valid entry"); + let got = index.get_by_uri("rsync://example.test/repo/b.roa").unwrap(); + assert_eq!(got.current_hash_hex, "22".repeat(32)); + } + + #[test] + fn current_repo_index_withdraw_unknown_uri_is_noop() { + let mut index = CurrentRepoIndex::new(); + index + .apply_repository_view_entries(&[RepositoryViewEntry { + rsync_uri: "rsync://example.test/repo/missing.roa".to_string(), + current_hash: None, + repository_source: Some("rsync://example.test/repo/".to_string()), + object_type: Some("roa".to_string()), + state: RepositoryViewState::Withdrawn, + }]) + .expect("withdraw unknown should not fail"); + assert_eq!(index.active_uri_count(), 0); + assert_eq!(index.scope_count(), 0); + } +} diff --git a/src/repository/fetch/current_repository.rs b/src/repository/fetch/current_repository.rs new file mode 100644 index 0000000..13b1f65 --- /dev/null +++ b/src/repository/fetch/current_repository.rs @@ -0,0 +1,234 @@ +use std::sync::Arc; + +use crate::repository::fetch::rsync::{RsyncFetchError, RsyncFetchResult, RsyncFetcher}; +use crate::repository::fetch::rsync_system::{ + RsyncScopePolicy, scoped_rsync_failure_dedup_key, scoped_rsync_fetch_uri, +}; +use crate::repository::storage::{RepositoryViewState, RocksStore}; + +#[derive(Clone)] +pub struct CurrentRepositoryViewRsyncFetcher { + store: Arc, + scope_policy: RsyncScopePolicy, +} + +impl CurrentRepositoryViewRsyncFetcher { + pub fn new(store: Arc, scope_policy: RsyncScopePolicy) -> Self { + Self { + store, + scope_policy, + } + } +} + +impl RsyncFetcher for CurrentRepositoryViewRsyncFetcher { + fn fetch_objects(&self, rsync_base_uri: &str) -> RsyncFetchResult)>> { + // The source run's live fetcher may have fetched a module root rather + // than the publication-point URI that triggered it. Read that same + // frozen-view prefix here so the materialized object set stays + // equivalent to the publication-point URI that triggered the fetch. + let base = scoped_rsync_fetch_uri(self.scope_policy, rsync_base_uri); + let entries = self + .store + .list_repository_view_entries_with_prefix(&base) + .map_err(|error| { + RsyncFetchError::Fetch(format!( + "list frozen repository view failed for {base}: {error}" + )) + })?; + let mut objects = Vec::with_capacity(entries.len()); + for entry in entries { + if !matches!( + entry.state, + RepositoryViewState::Present | RepositoryViewState::Replaced + ) { + continue; + } + let bytes = self + .store + .load_current_object_bytes_by_uri(&entry.rsync_uri) + .map_err(|error| { + RsyncFetchError::Fetch(format!( + "load frozen repository object failed for {}: {error}", + entry.rsync_uri + )) + })? + .ok_or_else(|| { + RsyncFetchError::Fetch(format!( + "frozen repository object missing for {}", + entry.rsync_uri + )) + })?; + objects.push((entry.rsync_uri, bytes)); + } + objects.sort_by(|left, right| left.0.cmp(&right.0)); + if objects.is_empty() { + return Err(RsyncFetchError::Fetch(format!( + "frozen repository view contains no current objects under {base}" + ))); + } + Ok(objects) + } + + fn fetch_object(&self, rsync_uri: &str) -> RsyncFetchResult> { + self.store + .load_current_object_bytes_by_uri(rsync_uri) + .map_err(|error| { + RsyncFetchError::Fetch(format!( + "load frozen repository object failed for {rsync_uri}: {error}" + )) + })? + .ok_or_else(|| { + RsyncFetchError::Fetch(format!("frozen repository object not found: {rsync_uri}")) + }) + } + + fn dedup_key(&self, rsync_base_uri: &str) -> String { + scoped_rsync_fetch_uri(self.scope_policy, rsync_base_uri) + } + + fn failure_dedup_key(&self, rsync_base_uri: &str) -> Option { + scoped_rsync_failure_dedup_key(self.scope_policy, rsync_base_uri) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::repository::storage::{RepositoryViewEntry, RocksStore}; + + #[test] + fn current_repository_fetcher_returns_sorted_present_objects() { + let dir = tempfile::tempdir().expect("tempdir"); + let store = Arc::new(RocksStore::open(dir.path()).expect("open store")); + let entries = [ + ("rsync://example.test/repo/b.roa", b"b".as_slice()), + ("rsync://example.test/repo/a.mft", b"a".as_slice()), + ]; + for (uri, bytes) in entries { + let hash = crate::repository::sync::store_projection::compute_sha256_hex(bytes); + store + .put_blob_bytes_batch(&[(hash.clone(), bytes.to_vec())]) + .expect("put blob"); + store + .put_repository_view_entry(&RepositoryViewEntry { + rsync_uri: uri.to_string(), + repository_source: Some("fixture".to_string()), + object_type: Some("roa".to_string()), + state: RepositoryViewState::Present, + current_hash: Some(hash), + }) + .expect("put view"); + } + let fetcher = CurrentRepositoryViewRsyncFetcher::new(store, RsyncScopePolicy::default()); + let objects = fetcher + .fetch_objects("rsync://example.test/repo/") + .expect("fetch objects"); + assert_eq!( + objects + .iter() + .map(|(uri, _)| uri.as_str()) + .collect::>(), + vec![ + "rsync://example.test/repo/a.mft", + "rsync://example.test/repo/b.roa" + ] + ); + assert_eq!( + fetcher + .fetch_object("rsync://example.test/repo/a.mft") + .expect("fetch one"), + b"a" + ); + assert!( + fetcher + .fetch_object("rsync://example.test/repo/missing.roa") + .is_err() + ); + assert_eq!( + fetcher.dedup_key("rsync://example.test/repo"), + "rsync://example.test/repo/" + ); + assert!( + fetcher + .fetch_objects("rsync://empty.example/repo/") + .unwrap_err() + .to_string() + .contains("no current objects") + ); + } + + #[test] + fn current_repository_fetcher_ignores_withdrawn_and_reports_missing_blob() { + let dir = tempfile::tempdir().expect("tempdir"); + let store = Arc::new(RocksStore::open(dir.path()).expect("open store")); + store + .put_repository_view_entry(&RepositoryViewEntry { + rsync_uri: "rsync://example.test/repo/withdrawn.roa".to_string(), + repository_source: Some("fixture".to_string()), + object_type: Some("roa".to_string()), + state: RepositoryViewState::Withdrawn, + current_hash: None, + }) + .expect("put withdrawn view"); + let missing_hash = "ab".repeat(32); + store + .put_repository_view_entry(&RepositoryViewEntry { + rsync_uri: "rsync://example.test/repo/missing.roa".to_string(), + repository_source: Some("fixture".to_string()), + object_type: Some("roa".to_string()), + state: RepositoryViewState::Present, + current_hash: Some(missing_hash), + }) + .expect("put missing view"); + let fetcher = CurrentRepositoryViewRsyncFetcher::new(store, RsyncScopePolicy::default()); + let error = fetcher + .fetch_objects("rsync://example.test/repo/") + .unwrap_err() + .to_string(); + assert!(error.contains("blob bytes missing")); + } + + #[test] + fn current_repository_fetcher_uses_module_scope_and_failure_dedup() { + let dir = tempfile::tempdir().expect("tempdir"); + let store = Arc::new(RocksStore::open(dir.path()).expect("open store")); + for (uri, bytes) in [ + ("rsync://example.test/repo/ca/a.mft", b"a".as_slice()), + ("rsync://example.test/repo/other/b.roa", b"b".as_slice()), + ] { + let hash = crate::repository::sync::store_projection::compute_sha256_hex(bytes); + store + .put_blob_bytes_batch(&[(hash.clone(), bytes.to_vec())]) + .expect("put blob"); + store + .put_repository_view_entry(&RepositoryViewEntry { + rsync_uri: uri.to_string(), + repository_source: Some("fixture".to_string()), + object_type: Some("fixture".to_string()), + state: RepositoryViewState::Present, + current_hash: Some(hash), + }) + .expect("put view"); + } + + let module_fetcher = CurrentRepositoryViewRsyncFetcher::new( + Arc::clone(&store), + RsyncScopePolicy::ModuleRoot, + ); + let objects = module_fetcher + .fetch_objects("rsync://example.test/repo/ca/") + .expect("fetch module scope"); + assert_eq!(objects.len(), 2); + assert_eq!( + module_fetcher.dedup_key("rsync://example.test/repo/ca/"), + "rsync://example.test/repo/" + ); + + let host_fetcher = CurrentRepositoryViewRsyncFetcher::new(store, RsyncScopePolicy::Host); + assert_eq!( + host_fetcher.failure_dedup_key("rsync://example.test/repo/ca/"), + Some("rsync://example.test/".to_string()) + ); + } +} diff --git a/src/repository/fetch/http.rs b/src/repository/fetch/http.rs new file mode 100644 index 0000000..404b7d4 --- /dev/null +++ b/src/repository/fetch/http.rs @@ -0,0 +1,922 @@ +use std::cell::RefCell; +use std::io::Write; +use std::time::Duration; + +use reqwest::blocking::Client; +use reqwest::header::{HeaderMap, LOCATION}; +use url::Url; + +use crate::repository::sync::rrdp::{ + Fetcher, RrdpError, RrdpFetchError, RrdpOrigin, RrdpResourceKind, +}; + +const MAX_RRDP_REDIRECTS: usize = 10; + +thread_local! { + static HTTP_TIMEOUT_OVERRIDE: RefCell> = const { RefCell::new(None) }; +} + +pub fn with_scoped_http_timeout_override(timeout: Duration, f: impl FnOnce() -> R) -> R { + HTTP_TIMEOUT_OVERRIDE.with(|cell| { + let previous = cell.replace(Some(timeout)); + let result = f(); + let _ = cell.replace(previous); + result + }) +} + +/// Default User-Agent sent with every outgoing HTTP request (RRDP +/// notification/snapshot/delta and TAL/TA downloads). +pub const DEFAULT_HTTP_USER_AGENT: &str = "panda-rpki/0.2"; + +/// Environment variable that overrides the HTTP User-Agent. Unset, empty, or +/// values containing characters that are illegal in a header value fall back +/// to [`DEFAULT_HTTP_USER_AGENT`]. +pub const HTTP_USER_AGENT_ENV: &str = "RPKI_HTTP_USER_AGENT"; + +fn resolve_http_user_agent(env_value: Option) -> String { + let Some(value) = env_value else { + return DEFAULT_HTTP_USER_AGENT.to_string(); + }; + let trimmed = value.trim(); + let valid = !trimmed.is_empty() && trimmed.bytes().all(|b| (0x20..=0x7e).contains(&b)); + if valid { + trimmed.to_string() + } else { + DEFAULT_HTTP_USER_AGENT.to_string() + } +} + +#[derive(Clone, Debug)] +pub struct HttpFetcherConfig { + /// Connection-establishment timeout for HTTP requests. + pub connect_timeout: Duration, + /// Short timeout used for connection establishment and small metadata objects. + pub timeout: Duration, + /// Larger timeout used for RRDP snapshot / delta bodies. + pub large_body_timeout: Duration, + pub user_agent: String, + /// Extra PEM trust anchors for HTTPS transport tests or controlled endpoints. + pub extra_root_certificates_pem: Vec>, +} + +impl Default for HttpFetcherConfig { + fn default() -> Self { + Self { + connect_timeout: Duration::from_secs(15), + timeout: Duration::from_secs(30), + large_body_timeout: Duration::from_secs(180), + user_agent: resolve_http_user_agent(std::env::var(HTTP_USER_AGENT_ENV).ok()), + extra_root_certificates_pem: Vec::new(), + } + } +} + +/// Minimal blocking HTTP(S) fetcher for validation runs. +/// +/// This is used for: +/// - downloading TAL / TA certificates (RFC 8630 §2) +/// - fetching RRDP notification/snapshot files (RFC 8182 §3.4) +#[derive(Clone, Debug)] +pub struct BlockingHttpFetcher { + short_client: Client, + large_body_client: Client, + retry_short_client: Client, + rrdp_short_client: Client, + rrdp_large_body_client: Client, + rrdp_retry_short_client: Client, + short_timeout: Duration, + large_body_timeout: Duration, +} + +impl BlockingHttpFetcher { + pub fn new(config: HttpFetcherConfig) -> Result { + let short_timeout = config.timeout; + let large_body_timeout = std::cmp::max(config.large_body_timeout, config.timeout); + let connect_timeout = std::cmp::min(config.connect_timeout, config.timeout); + let short_client = Self::client_builder( + &config, + connect_timeout, + config.timeout, + config.user_agent.clone(), + )? + .build() + .map_err(|e| e.to_string())?; + let large_body_client = Self::client_builder( + &config, + connect_timeout, + large_body_timeout, + config.user_agent.clone(), + )? + .build() + .map_err(|e| e.to_string())?; + let retry_short_client = Self::client_builder( + &config, + Duration::from_secs(1), + Duration::from_secs(1), + config.user_agent.clone(), + )? + .build() + .map_err(|e| e.to_string())?; + let rrdp_short_client = Self::rrdp_client_builder( + &config, + connect_timeout, + config.timeout, + config.user_agent.clone(), + )? + .build() + .map_err(|e| e.to_string())?; + let rrdp_large_body_client = Self::rrdp_client_builder( + &config, + connect_timeout, + large_body_timeout, + config.user_agent.clone(), + )? + .build() + .map_err(|e| e.to_string())?; + let rrdp_retry_short_client = Self::rrdp_client_builder( + &config, + Duration::from_secs(1), + Duration::from_secs(1), + config.user_agent.clone(), + )? + .build() + .map_err(|e| e.to_string())?; + Ok(Self { + short_client, + large_body_client, + retry_short_client, + rrdp_short_client, + rrdp_large_body_client, + rrdp_retry_short_client, + short_timeout, + large_body_timeout, + }) + } + + fn client_builder( + config: &HttpFetcherConfig, + connect_timeout: Duration, + timeout: Duration, + user_agent: String, + ) -> Result { + let mut builder = Client::builder() + .connect_timeout(connect_timeout) + .timeout(timeout) + .user_agent(user_agent); + for (idx, pem) in config.extra_root_certificates_pem.iter().enumerate() { + let certificate = reqwest::Certificate::from_pem(pem) + .map_err(|e| format!("parse HTTP root certificate #{idx} failed: {e}"))?; + builder = builder.add_root_certificate(certificate); + } + Ok(builder) + } + + fn rrdp_client_builder( + config: &HttpFetcherConfig, + connect_timeout: Duration, + timeout: Duration, + user_agent: String, + ) -> Result { + Ok( + Self::client_builder(config, connect_timeout, timeout, user_agent)? + .redirect(reqwest::redirect::Policy::none()), + ) + } + + pub fn fetch_bytes(&self, uri: &str) -> Result, String> { + let started = std::time::Instant::now(); + let (client, timeout_profile, timeout_value) = self.client_for_uri(uri); + let resp = client.get(uri).send().map_err(|e| { + let msg = format!("http request failed: {e:?}"); + crate::logging::progress::emit!( + "http_fetch_failed", + serde_json::json!({ + "uri": uri, + "stage": "request", + "timeout_profile": timeout_profile, + "request_timeout_ms": timeout_value.as_millis() as u64, + "duration_ms": started.elapsed().as_millis() as u64, + "error": msg, + }), + ); + msg + })?; + + let status = resp.status(); + let headers = resp.headers().clone(); + if !status.is_success() { + let msg = format!( + "http status {status}; content_type={}; content_encoding={}; content_length={}; transfer_encoding={}", + header_value(&headers, "content-type"), + header_value(&headers, "content-encoding"), + header_value(&headers, "content-length"), + header_value(&headers, "transfer-encoding"), + ); + crate::logging::progress::emit!( + "http_fetch_failed", + serde_json::json!({ + "uri": uri, + "stage": "status", + "timeout_profile": timeout_profile, + "request_timeout_ms": timeout_value.as_millis() as u64, + "duration_ms": started.elapsed().as_millis() as u64, + "status": status.as_u16(), + "content_type": header_value_opt(&headers, "content-type"), + "content_encoding": header_value_opt(&headers, "content-encoding"), + "content_length": header_value_opt(&headers, "content-length"), + "transfer_encoding": header_value_opt(&headers, "transfer-encoding"), + "error": msg, + }), + ); + return Err(msg); + } + + match resp.bytes() { + Ok(bytes) => { + let duration_ms = started.elapsed().as_millis() as u64; + if (duration_ms as f64) / 1000.0 >= crate::logging::progress::slow_threshold_secs() + { + crate::logging::progress::emit!( + "http_fetch_slow", + serde_json::json!({ + "uri": uri, + "status": status.as_u16(), + "timeout_profile": timeout_profile, + "request_timeout_ms": timeout_value.as_millis() as u64, + "duration_ms": duration_ms, + "bytes": bytes.len(), + "content_type": header_value_opt(&headers, "content-type"), + "content_encoding": header_value_opt(&headers, "content-encoding"), + "content_length": header_value_opt(&headers, "content-length"), + "transfer_encoding": header_value_opt(&headers, "transfer-encoding"), + }), + ); + } + Ok(bytes.to_vec()) + } + Err(e) => { + let msg = format!( + "http read body failed: {e}; status={}; content_type={}; content_encoding={}; content_length={}; transfer_encoding={}", + status, + header_value(&headers, "content-type"), + header_value(&headers, "content-encoding"), + header_value(&headers, "content-length"), + header_value(&headers, "transfer-encoding"), + ); + crate::logging::progress::emit!( + "http_fetch_failed", + serde_json::json!({ + "uri": uri, + "stage": "read_body", + "timeout_profile": timeout_profile, + "request_timeout_ms": timeout_value.as_millis() as u64, + "duration_ms": started.elapsed().as_millis() as u64, + "status": status.as_u16(), + "content_type": header_value_opt(&headers, "content-type"), + "content_encoding": header_value_opt(&headers, "content-encoding"), + "content_length": header_value_opt(&headers, "content-length"), + "transfer_encoding": header_value_opt(&headers, "transfer-encoding"), + "error": msg, + }), + ); + Err(msg) + } + } + } + + fn client_for_uri(&self, uri: &str) -> (&Client, &'static str, Duration) { + if let Some(timeout) = HTTP_TIMEOUT_OVERRIDE.with(|cell| *cell.borrow()) { + return (&self.retry_short_client, "retry_short", timeout); + } + if uses_large_body_timeout(uri) { + ( + &self.large_body_client, + "large_body", + self.large_body_timeout, + ) + } else { + (&self.short_client, "short", self.short_timeout) + } + } + + fn rrdp_client_for_uri(&self, uri: &str) -> (&Client, &'static str, Duration) { + if let Some(timeout) = HTTP_TIMEOUT_OVERRIDE.with(|cell| *cell.borrow()) { + return (&self.rrdp_retry_short_client, "retry_short", timeout); + } + if uses_large_body_timeout(uri) { + ( + &self.rrdp_large_body_client, + "large_body", + self.large_body_timeout, + ) + } else { + (&self.rrdp_short_client, "short", self.short_timeout) + } + } + + fn rrdp_response( + &self, + resource: RrdpResourceKind, + uri: &str, + notification_origin: &RrdpOrigin, + ) -> Result<(reqwest::blocking::Response, &'static str, Duration), RrdpFetchError> { + let mut current = Url::parse(uri).map_err(|e| RrdpError::InvalidRrdpUri { + resource, + detail: e.to_string(), + })?; + let current_origin = RrdpOrigin::from_url(¤t, resource)?; + if ¤t_origin != notification_origin { + return Err(RrdpError::CrossOriginReference { + resource, + notification_origin: notification_origin.to_string(), + resource_origin: current_origin.to_string(), + } + .into()); + } + + for redirects in 0..=MAX_RRDP_REDIRECTS { + let (client, profile, timeout) = self.rrdp_client_for_uri(current.as_str()); + let response = client + .get(current.clone()) + .send() + .map_err(|e| RrdpFetchError::Fetch(format!("http request failed: {e:?}")))?; + if !response.status().is_redirection() { + return Ok((response, profile, timeout)); + } + if redirects == MAX_RRDP_REDIRECTS { + return Err(RrdpError::RedirectLimitExceeded { + resource, + max: MAX_RRDP_REDIRECTS, + } + .into()); + } + let location = response + .headers() + .get(LOCATION) + .ok_or(RrdpError::RedirectLocationMissing { resource })? + .to_str() + .map_err(|_| RrdpError::InvalidRedirectLocation { + resource, + location: "".to_string(), + })?; + let next = current + .join(location) + .map_err(|_| RrdpError::InvalidRedirectLocation { + resource, + location: location.to_string(), + })?; + let next_origin = RrdpOrigin::from_url(&next, resource)?; + crate::logging::progress::emit!( + "rrdp_redirect_checked", + serde_json::json!({ + "resource": resource.to_string(), + "from": current.as_str(), + "to": next.as_str(), + "notification_origin": notification_origin.to_string(), + "same_origin": &next_origin == notification_origin, + "rfc": "RFC 9674 §3.2", + }), + ); + if &next_origin != notification_origin { + crate::logging::progress::emit!( + "rrdp_cross_origin_rejected", + serde_json::json!({ + "resource": resource.to_string(), + "notification_origin": notification_origin.to_string(), + "resource_origin": next_origin.to_string(), + "reason": "redirect", + "rfc": "RFC 9674 §3.2", + }), + ); + return Err(RrdpError::CrossOriginRedirect { + resource, + notification_origin: notification_origin.to_string(), + redirect_origin: next_origin.to_string(), + } + .into()); + } + current = next; + } + unreachable!("redirect loop either returns or reaches its limit") + } + + fn rrdp_fetch_bytes( + &self, + resource: RrdpResourceKind, + uri: &str, + notification_origin: &RrdpOrigin, + ) -> Result, RrdpFetchError> { + let (response, _profile, _timeout) = + self.rrdp_response(resource, uri, notification_origin)?; + let status = response.status(); + if !status.is_success() { + return Err(RrdpFetchError::Fetch(format!("http status {status}"))); + } + response + .bytes() + .map(|b| b.to_vec()) + .map_err(|e| RrdpFetchError::Fetch(format!("http read body failed: {e}"))) + } + + fn rrdp_fetch_to_writer( + &self, + resource: RrdpResourceKind, + uri: &str, + notification_origin: &RrdpOrigin, + out: &mut dyn Write, + ) -> Result { + let (mut response, _profile, _timeout) = + self.rrdp_response(resource, uri, notification_origin)?; + let status = response.status(); + if !status.is_success() { + return Err(RrdpFetchError::Fetch(format!("http status {status}"))); + } + response + .copy_to(out) + .map_err(|e| RrdpFetchError::Fetch(format!("http stream body failed: {e}"))) + } +} + +impl Fetcher for BlockingHttpFetcher { + fn fetch(&self, uri: &str) -> Result, String> { + self.fetch_bytes(uri) + } + + fn fetch_to_writer(&self, uri: &str, out: &mut dyn Write) -> Result { + let started = std::time::Instant::now(); + let (client, timeout_profile, timeout_value) = self.client_for_uri(uri); + let resp = client.get(uri).send().map_err(|e| { + let msg = format!("http request failed: {e:?}"); + crate::logging::progress::emit!( + "http_fetch_failed", + serde_json::json!({ + "uri": uri, + "stage": "request", + "timeout_profile": timeout_profile, + "request_timeout_ms": timeout_value.as_millis() as u64, + "duration_ms": started.elapsed().as_millis() as u64, + "error": msg, + }), + ); + msg + })?; + + let status = resp.status(); + let headers = resp.headers().clone(); + if !status.is_success() { + let msg = format!( + "http status {status}; content_type={}; content_encoding={}; content_length={}; transfer_encoding={}", + header_value(&headers, "content-type"), + header_value(&headers, "content-encoding"), + header_value(&headers, "content-length"), + header_value(&headers, "transfer-encoding"), + ); + crate::logging::progress::emit!( + "http_fetch_failed", + serde_json::json!({ + "uri": uri, + "stage": "status", + "timeout_profile": timeout_profile, + "request_timeout_ms": timeout_value.as_millis() as u64, + "duration_ms": started.elapsed().as_millis() as u64, + "status": status.as_u16(), + "content_type": header_value_opt(&headers, "content-type"), + "content_encoding": header_value_opt(&headers, "content-encoding"), + "content_length": header_value_opt(&headers, "content-length"), + "transfer_encoding": header_value_opt(&headers, "transfer-encoding"), + "error": msg, + }), + ); + return Err(msg); + } + + let mut resp = resp; + match resp.copy_to(out) { + Ok(bytes) => { + let duration_ms = started.elapsed().as_millis() as u64; + if (duration_ms as f64) / 1000.0 >= crate::logging::progress::slow_threshold_secs() + { + crate::logging::progress::emit!( + "http_fetch_slow", + serde_json::json!({ + "uri": uri, + "status": status.as_u16(), + "timeout_profile": timeout_profile, + "request_timeout_ms": timeout_value.as_millis() as u64, + "duration_ms": duration_ms, + "bytes": bytes, + "content_type": header_value_opt(&headers, "content-type"), + "content_encoding": header_value_opt(&headers, "content-encoding"), + "content_length": header_value_opt(&headers, "content-length"), + "transfer_encoding": header_value_opt(&headers, "transfer-encoding"), + }), + ); + } + Ok(bytes) + } + Err(e) => { + let msg = format!( + "http stream body failed: {e}; status={}; content_type={}; content_encoding={}; content_length={}; transfer_encoding={}", + status, + header_value(&headers, "content-type"), + header_value(&headers, "content-encoding"), + header_value(&headers, "content-length"), + header_value(&headers, "transfer-encoding"), + ); + crate::logging::progress::emit!( + "http_fetch_failed", + serde_json::json!({ + "uri": uri, + "stage": "stream_body", + "timeout_profile": timeout_profile, + "request_timeout_ms": timeout_value.as_millis() as u64, + "duration_ms": started.elapsed().as_millis() as u64, + "status": status.as_u16(), + "content_type": header_value_opt(&headers, "content-type"), + "content_encoding": header_value_opt(&headers, "content-encoding"), + "content_length": header_value_opt(&headers, "content-length"), + "transfer_encoding": header_value_opt(&headers, "transfer-encoding"), + "error": msg, + }), + ); + Err(msg) + } + } + } + + fn fetch_rrdp( + &self, + resource: RrdpResourceKind, + uri: &str, + notification_origin: &RrdpOrigin, + ) -> Result, RrdpFetchError> { + self.rrdp_fetch_bytes(resource, uri, notification_origin) + } + + fn fetch_rrdp_to_writer( + &self, + resource: RrdpResourceKind, + uri: &str, + notification_origin: &RrdpOrigin, + out: &mut dyn Write, + ) -> Result { + self.rrdp_fetch_to_writer(resource, uri, notification_origin, out) + } +} + +fn header_value(headers: &HeaderMap, name: &str) -> String { + header_value_opt(headers, name).unwrap_or_else(|| "".to_string()) +} + +fn header_value_opt(headers: &HeaderMap, name: &str) -> Option { + headers + .get(name) + .and_then(|v| v.to_str().ok()) + .map(|v| v.to_string()) +} + +fn uses_large_body_timeout(uri: &str) -> bool { + uri.starts_with("https://") && uri.ends_with(".xml") && !uri.ends_with("notification.xml") +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::{Read, Write}; + use std::net::TcpListener; + use std::sync::Arc; + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::thread; + use std::time::Duration as StdDuration; + + fn spawn_one_shot_http_server(status_line: &'static str, body: &'static [u8]) -> String { + let listener = TcpListener::bind(("127.0.0.1", 0)).expect("bind"); + let addr = listener.local_addr().expect("addr"); + thread::spawn(move || { + let (mut stream, _) = listener.accept().expect("accept"); + let mut buf = [0u8; 1024]; + let _ = stream.read(&mut buf); + let hdr = format!( + "{status_line}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n", + body.len() + ); + stream.write_all(hdr.as_bytes()).expect("write hdr"); + stream.write_all(body).expect("write body"); + }); + format!("http://{}/", addr) + } + + #[test] + fn fetch_bytes_returns_body_on_success() { + let url = spawn_one_shot_http_server("HTTP/1.1 200 OK", b"hello"); + let http = BlockingHttpFetcher::new(HttpFetcherConfig { + timeout: Duration::from_secs(2), + ..HttpFetcherConfig::default() + }) + .expect("http"); + let got = http.fetch_bytes(&url).expect("fetch"); + assert_eq!(got, b"hello"); + } + + #[test] + fn fetch_bytes_rejects_non_success_status() { + let url = spawn_one_shot_http_server("HTTP/1.1 404 Not Found", b""); + let http = BlockingHttpFetcher::new(HttpFetcherConfig { + timeout: Duration::from_secs(2), + ..HttpFetcherConfig::default() + }) + .expect("http"); + let err = http.fetch_bytes(&url).unwrap_err(); + assert!(err.contains("http status"), "{err}"); + } + + #[test] + fn fetch_bytes_times_out_on_idle_body_read() { + let listener = TcpListener::bind(("127.0.0.1", 0)).expect("bind"); + let addr = listener.local_addr().expect("addr"); + thread::spawn(move || { + let (mut stream, _) = listener.accept().expect("accept"); + let mut buf = [0u8; 1024]; + let _ = stream.read(&mut buf); + stream + .write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 5\r\nConnection: close\r\n\r\nh") + .expect("write partial body"); + std::thread::sleep(StdDuration::from_secs(2)); + let _ = stream.write_all(b"ello"); + }); + let url = format!("http://{}/", addr); + let http = BlockingHttpFetcher::new(HttpFetcherConfig { + timeout: Duration::from_secs(1), + ..HttpFetcherConfig::default() + }) + .expect("http"); + let err = http.fetch_bytes(&url).unwrap_err(); + assert!(err.contains("http read body failed"), "{err}"); + } + + #[test] + fn fetch_to_writer_streams_body_on_success() { + let url = spawn_one_shot_http_server("HTTP/1.1 200 OK", b"writer-body"); + let http = BlockingHttpFetcher::new(HttpFetcherConfig { + timeout: Duration::from_secs(2), + ..HttpFetcherConfig::default() + }) + .expect("http"); + let mut out = Vec::new(); + let bytes = http.fetch_to_writer(&url, &mut out).expect("stream"); + assert_eq!(bytes, 11); + assert_eq!(out, b"writer-body"); + } + + #[test] + fn fetch_to_writer_rejects_non_success_status() { + let url = spawn_one_shot_http_server("HTTP/1.1 500 Internal Server Error", b"boom"); + let http = BlockingHttpFetcher::new(HttpFetcherConfig { + timeout: Duration::from_secs(2), + ..HttpFetcherConfig::default() + }) + .expect("http"); + let mut out = Vec::new(); + let err = http.fetch_to_writer(&url, &mut out).unwrap_err(); + assert!(err.contains("http status"), "{err}"); + assert!(out.is_empty()); + } + + #[test] + fn fetch_to_writer_times_out_on_idle_stream_read() { + let listener = TcpListener::bind(("127.0.0.1", 0)).expect("bind"); + let addr = listener.local_addr().expect("addr"); + thread::spawn(move || { + let (mut stream, _) = listener.accept().expect("accept"); + let mut buf = [0u8; 1024]; + let _ = stream.read(&mut buf); + stream + .write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 5\r\nConnection: close\r\n\r\nh") + .expect("write partial body"); + std::thread::sleep(StdDuration::from_secs(2)); + let _ = stream.write_all(b"ello"); + }); + let url = format!("http://{}/", addr); + let http = BlockingHttpFetcher::new(HttpFetcherConfig { + timeout: Duration::from_secs(1), + ..HttpFetcherConfig::default() + }) + .expect("http"); + let mut out = Vec::new(); + let err = http.fetch_to_writer(&url, &mut out).unwrap_err(); + assert!(err.contains("http stream body failed"), "{err}"); + } + + #[test] + fn uses_large_body_timeout_selects_rrdp_snapshot_and_delta_not_notification() { + assert!(!uses_large_body_timeout( + "https://rrdp.example.test/notification.xml" + )); + assert!(uses_large_body_timeout( + "https://rrdp.example.test/session/123/snapshot.xml" + )); + assert!(uses_large_body_timeout( + "https://rrdp.example.test/session/123/delta-42.xml" + )); + assert!(!uses_large_body_timeout( + "https://tal.example.test/example.tal" + )); + } + + #[test] + fn client_for_uri_selects_expected_timeout_profile() { + let http = BlockingHttpFetcher::new(HttpFetcherConfig { + timeout: Duration::from_secs(3), + large_body_timeout: Duration::from_secs(9), + ..HttpFetcherConfig::default() + }) + .expect("http"); + + let (_, profile_short, timeout_short) = + http.client_for_uri("https://example.test/root.tal"); + assert_eq!(profile_short, "short"); + assert_eq!(timeout_short, Duration::from_secs(3)); + + let (_, profile_large, timeout_large) = + http.client_for_uri("https://rrdp.example.test/session/1/snapshot.xml"); + assert_eq!(profile_large, "large_body"); + assert_eq!(timeout_large, Duration::from_secs(9)); + } + + #[test] + fn resolve_http_user_agent_falls_back_to_default() { + assert_eq!(resolve_http_user_agent(None), DEFAULT_HTTP_USER_AGENT); + assert_eq!( + resolve_http_user_agent(Some(String::new())), + DEFAULT_HTTP_USER_AGENT + ); + assert_eq!( + resolve_http_user_agent(Some(" ".to_string())), + DEFAULT_HTTP_USER_AGENT + ); + // Control characters are illegal in header values; fall back instead + // of letting reqwest fail to build the client. + assert_eq!( + resolve_http_user_agent(Some("bad\u{1}ua".to_string())), + DEFAULT_HTTP_USER_AGENT + ); + assert_eq!( + resolve_http_user_agent(Some("non-ascii-ua-中".to_string())), + DEFAULT_HTTP_USER_AGENT + ); + } + + #[test] + fn resolve_http_user_agent_accepts_custom_value() { + assert_eq!( + resolve_http_user_agent(Some("panda-rpki/9.9 (test)".to_string())), + "panda-rpki/9.9 (test)" + ); + // Surrounding whitespace is trimmed. + assert_eq!( + resolve_http_user_agent(Some(" custom/1.0 ".to_string())), + "custom/1.0" + ); + } + + fn spawn_user_agent_capture_server() -> (String, std::sync::mpsc::Receiver) { + let listener = TcpListener::bind(("127.0.0.1", 0)).expect("bind"); + let addr = listener.local_addr().expect("addr"); + let (tx, rx) = std::sync::mpsc::channel(); + thread::spawn(move || { + let (mut stream, _) = listener.accept().expect("accept"); + let mut buf = [0u8; 4096]; + let read = stream.read(&mut buf).expect("read request"); + let request = String::from_utf8_lossy(&buf[..read]).to_string(); + let body = b"ok"; + let hdr = format!( + "HTTP/1.1 200 OK\r\nContent-Length: {}\r\nConnection: close\r\n\r\n", + body.len() + ); + stream.write_all(hdr.as_bytes()).expect("write hdr"); + stream.write_all(body).expect("write body"); + let _ = tx.send(request); + }); + (format!("http://{}/", addr), rx) + } + + #[test] + fn fetch_bytes_sends_configured_user_agent_header() { + let (url, rx) = spawn_user_agent_capture_server(); + let http = BlockingHttpFetcher::new(HttpFetcherConfig { + timeout: Duration::from_secs(2), + user_agent: "panda-rpki/0.2".to_string(), + ..HttpFetcherConfig::default() + }) + .expect("http"); + http.fetch_bytes(&url).expect("fetch"); + let request = rx.recv().expect("request captured"); + assert!( + request + .to_ascii_lowercase() + .contains("user-agent: panda-rpki/0.2"), + "{request}" + ); + } + + #[test] + fn fetch_bytes_sends_custom_user_agent_header() { + let (url, rx) = spawn_user_agent_capture_server(); + let http = BlockingHttpFetcher::new(HttpFetcherConfig { + timeout: Duration::from_secs(2), + user_agent: "custom-marker/7.7".to_string(), + ..HttpFetcherConfig::default() + }) + .expect("http"); + http.fetch_bytes(&url).expect("fetch"); + let request = rx.recv().expect("request captured"); + assert!( + request + .to_ascii_lowercase() + .contains("user-agent: custom-marker/7.7"), + "{request}" + ); + } + + #[test] + fn rrdp_same_origin_redirect_is_followed_manually() { + let listener = TcpListener::bind(("127.0.0.1", 0)).expect("bind"); + let addr = listener.local_addr().expect("addr"); + thread::spawn(move || { + for response in [ + b"HTTP/1.1 302 Found\r\nLocation: /final\r\nContent-Length: 0\r\nConnection: close\r\n\r\n" + .as_slice(), + b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok".as_slice(), + ] { + let (mut stream, _) = listener.accept().expect("accept"); + let mut buf = [0u8; 1024]; + let _ = stream.read(&mut buf); + stream.write_all(response).expect("response"); + } + }); + let uri = format!("http://{addr}/start"); + let origin = RrdpOrigin::parse(&uri, RrdpResourceKind::Notification).expect("origin"); + let http = BlockingHttpFetcher::new(HttpFetcherConfig { + timeout: Duration::from_secs(2), + ..HttpFetcherConfig::default() + }) + .expect("http"); + let body = http + .fetch_rrdp(RrdpResourceKind::Notification, &uri, &origin) + .expect("same-origin redirect accepted"); + assert_eq!(body, b"ok"); + } + + #[test] + fn rrdp_cross_origin_redirect_is_rejected_before_target_request() { + let target = TcpListener::bind(("127.0.0.1", 0)).expect("bind target"); + target.set_nonblocking(true).expect("nonblocking target"); + let target_addr = target.local_addr().expect("target addr"); + let target_hits = Arc::new(AtomicUsize::new(0)); + let target_hits_worker = Arc::clone(&target_hits); + let target_thread = thread::spawn(move || { + let deadline = std::time::Instant::now() + StdDuration::from_millis(400); + while std::time::Instant::now() < deadline { + match target.accept() { + Ok((mut stream, _)) => { + target_hits_worker.fetch_add(1, Ordering::SeqCst); + let _ = stream.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + } + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { + std::thread::sleep(StdDuration::from_millis(5)); + } + Err(e) => panic!("target accept: {e}"), + } + } + }); + + let source = TcpListener::bind(("127.0.0.1", 0)).expect("bind source"); + let source_addr = source.local_addr().expect("source addr"); + thread::spawn(move || { + let (mut stream, _) = source.accept().expect("accept source"); + let mut buf = [0u8; 1024]; + let _ = stream.read(&mut buf); + let response = format!( + "HTTP/1.1 302 Found\r\nLocation: http://{target_addr}/foreign\r\nContent-Length: 0\r\nConnection: close\r\n\r\n" + ); + stream.write_all(response.as_bytes()).expect("redirect"); + }); + let uri = format!("http://{source_addr}/start"); + let origin = RrdpOrigin::parse(&uri, RrdpResourceKind::Notification).expect("origin"); + let http = BlockingHttpFetcher::new(HttpFetcherConfig { + timeout: Duration::from_secs(2), + ..HttpFetcherConfig::default() + }) + .expect("http"); + let err = http + .fetch_rrdp(RrdpResourceKind::Notification, &uri, &origin) + .unwrap_err(); + assert!(matches!( + err, + RrdpFetchError::Rrdp(RrdpError::CrossOriginRedirect { .. }) + )); + target_thread.join().expect("target thread"); + assert_eq!(target_hits.load(Ordering::SeqCst), 0); + } +} diff --git a/src/repository/fetch/mod.rs b/src/repository/fetch/mod.rs new file mode 100644 index 0000000..5f92b39 --- /dev/null +++ b/src/repository/fetch/mod.rs @@ -0,0 +1,4 @@ +pub mod current_repository; +pub mod http; +pub mod rsync; +pub mod rsync_system; diff --git a/src/repository/fetch/rsync.rs b/src/repository/fetch/rsync.rs new file mode 100644 index 0000000..da8676c --- /dev/null +++ b/src/repository/fetch/rsync.rs @@ -0,0 +1,214 @@ +use std::path::{Path, PathBuf}; + +#[derive(Debug, thiserror::Error)] +pub enum RsyncFetchError { + #[error("rsync fetch error: {0}")] + Fetch(String), +} + +pub type RsyncFetchResult = Result; + +pub fn normalize_rsync_base_uri(s: &str) -> String { + if s.ends_with('/') { + s.to_string() + } else { + format!("{s}/") + } +} + +/// Fetch repository objects from a publication point. +/// +/// v1: this is intentionally abstract so unit tests can use a mock, and later we can +/// back it by calling the system `rsync` binary (RFC 6481 §5; RFC 8182 §3.4.5). +pub trait RsyncFetcher: Send + Sync { + /// Return a list of objects as `(rsync_uri, bytes)` pairs. + fn fetch_objects(&self, rsync_base_uri: &str) -> RsyncFetchResult)>>; + + /// Fetch one object by exact rsync URI. + /// + /// The default implementation fetches the parent directory and filters the exact + /// object. Live fetchers should override this to avoid widening one-object TAL + /// bootstrap fetches into whole publication point or module synchronizations. + fn fetch_object(&self, rsync_uri: &str) -> RsyncFetchResult> { + let base = parent_rsync_uri(rsync_uri).map_err(RsyncFetchError::Fetch)?; + self.fetch_objects(&base)? + .into_iter() + .find(|(uri, _)| uri == rsync_uri) + .map(|(_, bytes)| bytes) + .ok_or_else(|| RsyncFetchError::Fetch(format!("rsync object not found: {rsync_uri}"))) + } + + /// Stream fetched objects to a visitor without requiring callers to materialize the + /// full result vector in memory. + fn visit_objects( + &self, + rsync_base_uri: &str, + visitor: &mut dyn FnMut(String, Vec) -> Result<(), String>, + ) -> RsyncFetchResult<(usize, u64)> { + let objects = self.fetch_objects(rsync_base_uri)?; + let mut count = 0usize; + let mut bytes_total = 0u64; + for (uri, bytes) in objects { + bytes_total += bytes.len() as u64; + count += 1; + visitor(uri, bytes).map_err(RsyncFetchError::Fetch)?; + } + Ok((count, bytes_total)) + } + + /// Return the deduplication key used by orchestration layers. + /// + /// By default this is the normalized publication point base URI. Fetchers that + /// intentionally widen their fetch scope (for example to a full rsync module) + /// should override this so callers can safely deduplicate at the same scope. + fn dedup_key(&self, rsync_base_uri: &str) -> String { + normalize_rsync_base_uri(rsync_base_uri) + } + + /// Return an optional failure-only deduplication key. + /// + /// This key is only used to short-circuit repeated failed rsync fallbacks. It must + /// not be used to reuse successful fetch results unless it is identical to + /// `dedup_key`, because a successful fetch for one publication point does not imply + /// that another publication point under the same host has been fetched. + fn failure_dedup_key(&self, _rsync_base_uri: &str) -> Option { + None + } +} + +fn parent_rsync_uri(rsync_uri: &str) -> Result { + let parsed = url::Url::parse(rsync_uri).map_err(|e| e.to_string())?; + if parsed.scheme() != "rsync" { + return Err(format!("not an rsync URI: {rsync_uri}")); + } + let host = parsed + .host_str() + .ok_or_else(|| format!("missing host in rsync URI: {rsync_uri}"))?; + let segments = parsed + .path_segments() + .ok_or_else(|| format!("missing path in rsync URI: {rsync_uri}"))? + .collect::>(); + if segments.is_empty() || segments.last().copied().unwrap_or_default().is_empty() { + return Err(format!( + "rsync URI must reference a file object: {rsync_uri}" + )); + } + let parent_segments = &segments[..segments.len() - 1]; + // Preserve an explicitly supplied port. Dropping it changes the URI + // identity and prevents the default one-object implementation from + // matching objects returned by a fetcher (notably for test/private + // publication points that specify :873 explicitly). + let authority = match parsed.port() { + Some(port) => format!("{host}:{port}"), + None => host.to_string(), + }; + let mut parent = format!("rsync://{authority}/"); + if !parent_segments.is_empty() { + parent.push_str(&parent_segments.join("/")); + parent.push('/'); + } + Ok(parent) +} + +/// A simple "rsync" implementation backed by a local directory. +/// +/// This is primarily meant for offline tests and fixtures. The key generation mimics rsync URIs: +/// `rsync_base_uri` + relative path (with `/` separators). +#[derive(Clone, Debug)] +pub struct LocalDirRsyncFetcher { + pub root_dir: PathBuf, +} + +impl LocalDirRsyncFetcher { + pub fn new(root_dir: impl Into) -> Self { + Self { + root_dir: root_dir.into(), + } + } +} + +impl RsyncFetcher for LocalDirRsyncFetcher { + fn fetch_objects(&self, rsync_base_uri: &str) -> RsyncFetchResult)>> { + let base = normalize_rsync_base_uri(rsync_base_uri); + let mut out = Vec::new(); + walk_dir_collect(&self.root_dir, &self.root_dir, &base, &mut out) + .map_err(RsyncFetchError::Fetch)?; + Ok(out) + } +} + +fn walk_dir_collect( + root: &Path, + current: &Path, + rsync_base_uri: &str, + out: &mut Vec<(String, Vec)>, +) -> Result<(), String> { + let rd = std::fs::read_dir(current).map_err(|e| e.to_string())?; + for entry in rd { + let entry = entry.map_err(|e| e.to_string())?; + let path = entry.path(); + let meta = entry.metadata().map_err(|e| e.to_string())?; + if meta.is_dir() { + walk_dir_collect(root, &path, rsync_base_uri, out)?; + continue; + } + if !meta.is_file() { + continue; + } + let rel = path + .strip_prefix(root) + .map_err(|e| e.to_string())? + .to_string_lossy() + .replace('\\', "/"); + let uri = format!("{rsync_base_uri}{rel}"); + let bytes = std::fs::read(&path).map_err(|e| e.to_string())?; + out.push((uri, bytes)); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn local_dir_rsync_fetcher_collects_files_and_normalizes_base_uri() { + let tmp = tempfile::tempdir().expect("tempdir"); + std::fs::create_dir_all(tmp.path().join("nested")).expect("mkdir"); + std::fs::write(tmp.path().join("a.mft"), b"a").expect("write"); + std::fs::write(tmp.path().join("nested").join("b.roa"), b"b").expect("write"); + + let f = LocalDirRsyncFetcher::new(tmp.path()); + let mut objects = f + .fetch_objects("rsync://example.net/repo") + .expect("fetch_objects"); + objects.sort_by(|(a, _), (b, _)| a.cmp(b)); + + assert_eq!(objects.len(), 2); + assert_eq!(objects[0].0, "rsync://example.net/repo/a.mft"); + assert_eq!(objects[0].1, b"a"); + assert_eq!(objects[1].0, "rsync://example.net/repo/nested/b.roa"); + assert_eq!(objects[1].1, b"b"); + } + + #[test] + fn local_dir_rsync_fetcher_reports_read_dir_errors() { + let tmp = tempfile::tempdir().expect("tempdir"); + let missing = tmp.path().join("missing"); + let f = LocalDirRsyncFetcher::new(missing); + let err = f.fetch_objects("rsync://example.net/repo").unwrap_err(); + match err { + RsyncFetchError::Fetch(msg) => assert!(!msg.is_empty()), + } + } + + #[test] + fn default_dedup_key_is_normalized_base_uri() { + let tmp = tempfile::tempdir().expect("tempdir"); + let fetcher = LocalDirRsyncFetcher::new(tmp.path()); + assert_eq!( + fetcher.dedup_key("rsync://example.net/repo"), + "rsync://example.net/repo/" + ); + } +} diff --git a/src/repository/fetch/rsync_system.rs b/src/repository/fetch/rsync_system.rs new file mode 100644 index 0000000..946dd28 --- /dev/null +++ b/src/repository/fetch/rsync_system.rs @@ -0,0 +1,927 @@ +use std::cell::RefCell; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::process::Stdio; +use std::thread; +use std::time::Duration; +use std::time::Instant; + +use sha2::Digest; +use uuid::Uuid; + +use crate::repository::fetch::rsync::{ + RsyncFetchError, RsyncFetchResult, RsyncFetcher, normalize_rsync_base_uri, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "kebab-case")] +#[derive(Default)] +pub enum RsyncScopePolicy { + Host, + PublicationPoint, + #[default] + ModuleRoot, +} + +impl RsyncScopePolicy { + pub fn parse_cli_value(value: &str) -> Result { + match value { + "host" => Ok(Self::Host), + "publication-point" => Ok(Self::PublicationPoint), + "module-root" => Ok(Self::ModuleRoot), + _ => Err(format!( + "invalid --rsync-scope: {value}; expected host, publication-point, or module-root" + )), + } + } +} + +#[derive(Clone, Debug)] +pub struct SystemRsyncConfig { + pub rsync_bin: PathBuf, + pub connect_timeout: Duration, + pub timeout: Duration, + pub extra_args: Vec, + /// Optional root directory for persistent rsync mirrors. + /// + /// When set, callers may choose to sync into stable subdirectories under this + /// root (instead of a temporary directory) to benefit from rsync's incremental + /// behavior across runs. + /// + /// Note: actual mirror behavior is implemented separately from config wiring. + pub mirror_root: Option, + pub scope_policy: RsyncScopePolicy, +} + +impl Default for SystemRsyncConfig { + fn default() -> Self { + Self { + rsync_bin: PathBuf::from("rsync"), + connect_timeout: Duration::from_secs(15), + timeout: Duration::from_secs(30), + extra_args: Vec::new(), + mirror_root: None, + scope_policy: RsyncScopePolicy::default(), + } + } +} + +/// A `RsyncFetcher` implementation backed by the system `rsync` binary. +/// +/// This is intended for live synchronization runs. For unit tests and local fixtures, +/// prefer `LocalDirRsyncFetcher`. +#[derive(Clone, Debug)] +pub struct SystemRsyncFetcher { + config: SystemRsyncConfig, +} + +thread_local! { + static RSYNC_TIMEOUT_OVERRIDE: RefCell> = const { RefCell::new(None) }; + static RSYNC_FAIL_FAST_PROFILE: RefCell> = const { RefCell::new(None) }; +} + +pub fn with_scoped_rsync_timeout_override(timeout: Duration, f: impl FnOnce() -> R) -> R { + RSYNC_TIMEOUT_OVERRIDE.with(|cell| { + let previous = cell.replace(Some(timeout)); + let result = f(); + let _ = cell.replace(previous); + result + }) +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct RsyncFailFastProfile { + pub initial_wall_clock_timeout: Duration, + pub max_wall_clock_timeout: Duration, + pub max_attempts: usize, +} + +pub fn with_scoped_rsync_fail_fast_profile( + profile: RsyncFailFastProfile, + f: impl FnOnce() -> R, +) -> R { + RSYNC_FAIL_FAST_PROFILE.with(|cell| { + let previous = cell.replace(Some(profile)); + let result = f(); + let _ = cell.replace(previous); + result + }) +} + +impl SystemRsyncFetcher { + pub fn new(config: SystemRsyncConfig) -> Self { + Self { config } + } + + fn mirror_dst_dir(&self, normalized_rsync_base_uri: &str) -> Result, String> { + let Some(root) = self.config.mirror_root.as_ref() else { + return Ok(None); + }; + + std::fs::create_dir_all(root) + .map_err(|e| format!("create rsync mirror root failed: {}: {e}", root.display()))?; + + let hash = hex::encode(sha2::Sha256::digest(normalized_rsync_base_uri.as_bytes())); + let dir = root.join(hash); + std::fs::create_dir_all(&dir).map_err(|e| { + format!( + "create rsync mirror directory failed: {}: {e}", + dir.display() + ) + })?; + Ok(Some(dir)) + } + + fn run_rsync(&self, src: &str, dst: &Path) -> Result<(), String> { + let fail_fast = RSYNC_FAIL_FAST_PROFILE.with(|cell| *cell.borrow()); + if let Some(profile) = fail_fast { + return self.run_rsync_fail_fast(src, dst, profile); + } + self.run_rsync_once(src, dst, None, false) + } + + fn run_rsync_once( + &self, + src: &str, + dst: &Path, + wall_clock_timeout: Option, + keep_partial: bool, + ) -> Result<(), String> { + // `--timeout` is I/O timeout in seconds (applies to network reads/writes). + let timeout = + RSYNC_TIMEOUT_OVERRIDE.with(|cell| cell.borrow().unwrap_or(self.config.timeout)); + let connect_timeout_secs = self.config.connect_timeout.as_secs().max(1).to_string(); + let timeout_secs = timeout.as_secs().max(1).to_string(); + let is_remote_rsync = src.starts_with("rsync://"); + + let mut cmd = Command::new(&self.config.rsync_bin); + cmd.arg("-rt") + .arg("--delete") + .arg("--timeout") + .arg(timeout_secs) + .args(&self.config.extra_args); + if is_remote_rsync { + cmd.arg("--contimeout").arg(connect_timeout_secs); + } + if keep_partial { + cmd.arg("--partial"); + } + cmd.arg(src) + .arg(dst) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + let mut child = cmd + .spawn() + .map_err(|e| format!("rsync spawn failed: {e}"))?; + if let Some(limit) = wall_clock_timeout { + let started = Instant::now(); + loop { + match child + .try_wait() + .map_err(|e| format!("rsync wait failed: {e}"))? + { + Some(_status) => { + let out = child + .wait_with_output() + .map_err(|e| format!("rsync wait_with_output failed: {e}"))?; + if out.status.success() { + return Ok(()); + } + let stderr = String::from_utf8_lossy(&out.stderr); + let stdout = String::from_utf8_lossy(&out.stdout); + return Err(format!( + "rsync failed: status={} stdout={} stderr={}", + out.status, + stdout.trim(), + stderr.trim() + )); + } + None => { + if started.elapsed() >= limit { + let _ = child.kill(); + let out = child + .wait_with_output() + .map_err(|e| format!("rsync wait_with_output failed: {e}"))?; + let stderr = String::from_utf8_lossy(&out.stderr); + let stdout = String::from_utf8_lossy(&out.stdout); + return Err(format!( + "rsync wall-clock timeout after {}s: stdout={} stderr={}", + limit.as_secs(), + stdout.trim(), + stderr.trim() + )); + } + thread::sleep(Duration::from_millis(100)); + } + } + } + } + let out = child + .wait_with_output() + .map_err(|e| format!("rsync wait_with_output failed: {e}"))?; + if out.status.success() { + return Ok(()); + } + let stderr = String::from_utf8_lossy(&out.stderr); + let stdout = String::from_utf8_lossy(&out.stdout); + Err(format!( + "rsync failed: status={} stdout={} stderr={}", + out.status, + stdout.trim(), + stderr.trim() + )) + } + + fn run_rsync_fail_fast( + &self, + src: &str, + dst: &Path, + profile: RsyncFailFastProfile, + ) -> Result<(), String> { + let mut attempt = 0usize; + let mut timeout = profile.initial_wall_clock_timeout; + let mut previous_progress = (0usize, 0u64); + let mut zero_progress_attempts = 0usize; + let max_timeout = std::cmp::max( + profile.max_wall_clock_timeout, + profile.initial_wall_clock_timeout, + ); + + loop { + attempt += 1; + match self.run_rsync_once(src, dst, Some(timeout), true) { + Ok(()) => return Ok(()), + Err(err) => { + if is_hard_fail_rsync_error(&err) { + return Err(format!( + "rsync fail-fast hard-fail on attempt {}: {}", + attempt, err + )); + } + if !err.contains("wall-clock timeout") { + return Err(err); + } + let progress = dir_progress(dst) + .map_err(|e| format!("rsync fail-fast progress stat failed: {e}"))?; + if progress == (0, 0) { + zero_progress_attempts += 1; + if zero_progress_attempts >= 2 || attempt >= profile.max_attempts { + return Err(format!( + "rsync fail-fast gave up after {} attempts with no progress: {}", + attempt, err + )); + } + } else if progress == previous_progress { + return Err(format!( + "rsync fail-fast gave up after {} attempts with no additional progress: {}", + attempt, err + )); + } else { + previous_progress = progress; + } + + if attempt >= profile.max_attempts { + return Err(format!( + "rsync fail-fast exhausted {} attempts: {}", + profile.max_attempts, err + )); + } + timeout = std::cmp::min(timeout.saturating_mul(2), max_timeout); + } + } + } + } + + fn scope_fetch_uri(&self, rsync_base_uri: &str) -> String { + scoped_rsync_fetch_uri(self.config.scope_policy, rsync_base_uri) + } +} + +impl RsyncFetcher for SystemRsyncFetcher { + fn fetch_objects(&self, rsync_base_uri: &str) -> RsyncFetchResult)>> { + let mut out = Vec::new(); + self.visit_objects(rsync_base_uri, &mut |uri, bytes| { + out.push((uri, bytes)); + Ok(()) + })?; + Ok(out) + } + + fn fetch_object(&self, rsync_uri: &str) -> RsyncFetchResult> { + let parsed = + url::Url::parse(rsync_uri).map_err(|e| RsyncFetchError::Fetch(e.to_string()))?; + if parsed.scheme() != "rsync" { + return Err(RsyncFetchError::Fetch(format!( + "not an rsync URI: {rsync_uri}" + ))); + } + let file_name = parsed + .path_segments() + .and_then(|mut segments| segments.rfind(|segment| !segment.is_empty())) + .ok_or_else(|| { + RsyncFetchError::Fetch(format!( + "rsync URI must reference a file object: {rsync_uri}" + )) + })?; + let tmp = TempDir::new().map_err(RsyncFetchError::Fetch)?; + self.run_rsync(rsync_uri, tmp.path()) + .map_err(RsyncFetchError::Fetch)?; + let object_path = tmp.path().join(file_name); + std::fs::read(&object_path).map_err(|e| { + RsyncFetchError::Fetch(format!( + "read fetched rsync object failed: {}: {e}", + object_path.display() + )) + }) + } + + fn visit_objects( + &self, + rsync_base_uri: &str, + visitor: &mut dyn FnMut(String, Vec) -> Result<(), String>, + ) -> RsyncFetchResult<(usize, u64)> { + let base = self.scope_fetch_uri(rsync_base_uri); + let mut count = 0usize; + let mut bytes_total = 0u64; + let mut wrapped = |uri: String, bytes: Vec| -> Result<(), String> { + bytes_total += bytes.len() as u64; + count += 1; + visitor(uri, bytes) + }; + + if let Some(dst) = self + .mirror_dst_dir(&base) + .map_err(|e| RsyncFetchError::Fetch(e.to_string()))? + { + self.run_rsync(&base, &dst) + .map_err(RsyncFetchError::Fetch)?; + walk_dir_visit(&dst, &dst, &base, &mut wrapped).map_err(RsyncFetchError::Fetch)?; + return Ok((count, bytes_total)); + } + + let tmp = TempDir::new().map_err(|e| RsyncFetchError::Fetch(e.to_string()))?; + self.run_rsync(&base, tmp.path()) + .map_err(RsyncFetchError::Fetch)?; + walk_dir_visit(tmp.path(), tmp.path(), &base, &mut wrapped) + .map_err(RsyncFetchError::Fetch)?; + Ok((count, bytes_total)) + } + + fn dedup_key(&self, rsync_base_uri: &str) -> String { + self.scope_fetch_uri(rsync_base_uri) + } + + fn failure_dedup_key(&self, rsync_base_uri: &str) -> Option { + scoped_rsync_failure_dedup_key(self.config.scope_policy, rsync_base_uri) + } +} + +/// Return the exact successful-fetch scope used by the live rsync fetcher. +/// +/// The request scope is shared by live and materialized repository paths so their +/// request keys and object projection scope remain equivalent. +pub fn scoped_rsync_fetch_uri(scope_policy: RsyncScopePolicy, rsync_base_uri: &str) -> String { + match scope_policy { + RsyncScopePolicy::Host | RsyncScopePolicy::PublicationPoint => { + normalize_rsync_base_uri(rsync_base_uri) + } + RsyncScopePolicy::ModuleRoot => rsync_module_root_uri(rsync_base_uri) + .unwrap_or_else(|| normalize_rsync_base_uri(rsync_base_uri)), + } +} + +/// Return the live fetcher's failure-deduplication key for the configured +/// scope. This is part of the transport request identity, even though host +/// scope deliberately does not widen the successful fetch URI. +pub fn scoped_rsync_failure_dedup_key( + scope_policy: RsyncScopePolicy, + rsync_base_uri: &str, +) -> Option { + match scope_policy { + RsyncScopePolicy::Host => rsync_host_scope_uri(rsync_base_uri), + RsyncScopePolicy::PublicationPoint | RsyncScopePolicy::ModuleRoot => None, + } +} + +fn rsync_host_scope_uri(rsync_base_uri: &str) -> Option { + let parsed = url::Url::parse(rsync_base_uri).ok()?; + if parsed.scheme() != "rsync" { + return None; + } + Some(format!("rsync://{}/", parsed.host_str()?)) +} + +struct TempDir { + path: PathBuf, +} + +impl TempDir { + fn new() -> Result { + let mut p = std::env::temp_dir(); + p.push(format!("rpki-system-rsync-{}", Uuid::new_v4())); + std::fs::create_dir_all(&p).map_err(|e| e.to_string())?; + Ok(Self { path: p }) + } + + fn path(&self) -> &Path { + &self.path + } +} + +impl Drop for TempDir { + fn drop(&mut self) { + let _ = std::fs::remove_dir_all(&self.path); + } +} + +fn rsync_module_root_uri(s: &str) -> Option { + let normalized = normalize_rsync_base_uri(s); + let rest = normalized.strip_prefix("rsync://")?; + let (authority, path) = rest.split_once('/')?; + + let mut segments: Vec<&str> = path + .split('/') + .filter(|segment| !segment.is_empty()) + .collect(); + if segments.is_empty() { + return None; + } + let module = segments.remove(0); + Some(format!("rsync://{authority}/{module}/")) +} + +fn dir_progress(root: &Path) -> Result<(usize, u64), String> { + if !root.exists() { + return Ok((0, 0)); + } + let mut files = 0usize; + let mut bytes = 0u64; + let mut stack = vec![root.to_path_buf()]; + while let Some(path) = stack.pop() { + let rd = std::fs::read_dir(&path).map_err(|e| e.to_string())?; + for entry in rd { + let entry = entry.map_err(|e| e.to_string())?; + let path = entry.path(); + let meta = entry.metadata().map_err(|e| e.to_string())?; + if meta.is_dir() { + stack.push(path); + } else if meta.is_file() { + files += 1; + bytes += meta.len(); + } + } + } + Ok((files, bytes)) +} + +fn is_hard_fail_rsync_error(msg: &str) -> bool { + let lower = msg.to_ascii_lowercase(); + lower.contains("no route to host") + || lower.contains("network is unreachable") + || lower.contains("connection refused") + || lower.contains("name or service not known") +} + +fn walk_dir_visit( + root: &Path, + current: &Path, + rsync_base_uri: &str, + visitor: &mut dyn FnMut(String, Vec) -> Result<(), String>, +) -> Result<(), String> { + let rd = std::fs::read_dir(current).map_err(|e| e.to_string())?; + for entry in rd { + let entry = entry.map_err(|e| e.to_string())?; + let path = entry.path(); + let meta = entry.metadata().map_err(|e| e.to_string())?; + if meta.is_dir() { + walk_dir_visit(root, &path, rsync_base_uri, visitor)?; + continue; + } + if !meta.is_file() { + continue; + } + let rel = path + .strip_prefix(root) + .map_err(|e| e.to_string())? + .to_string_lossy() + .replace('\\', "/"); + let uri = format!("{rsync_base_uri}{rel}"); + let bytes = std::fs::read(&path).map_err(|e| e.to_string())?; + visitor(uri, bytes)?; + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn normalize_rsync_base_uri_appends_slash_when_missing() { + assert_eq!( + normalize_rsync_base_uri("rsync://example.net/repo"), + "rsync://example.net/repo/".to_string() + ); + assert_eq!( + normalize_rsync_base_uri("rsync://example.net/repo/"), + "rsync://example.net/repo/".to_string() + ); + } + + #[test] + fn walk_dir_collect_collects_files_and_normalizes_backslashes_in_uri() { + let temp = tempfile::tempdir().expect("tempdir"); + let root = temp.path(); + std::fs::create_dir_all(root.join("sub")).expect("mkdir"); + std::fs::write(root.join("sub").join("a.cer"), b"x").expect("write"); + std::fs::write(root.join("b\\c.mft"), b"y").expect("write backslash file"); + + let mut out: Vec<(String, Vec)> = Vec::new(); + walk_dir_visit( + root, + root, + "rsync://example.net/repo/", + &mut |uri, bytes| { + out.push((uri, bytes)); + Ok(()) + }, + ) + .expect("walk"); + out.sort_by(|a, b| a.0.cmp(&b.0)); + + assert_eq!(out.len(), 2); + assert_eq!(out[0].0, "rsync://example.net/repo/b/c.mft"); + assert_eq!(out[0].1, b"y"); + assert_eq!(out[1].0, "rsync://example.net/repo/sub/a.cer"); + assert_eq!(out[1].1, b"x"); + } + + #[test] + fn rsync_module_root_uri_returns_host_and_module_only() { + assert_eq!( + rsync_module_root_uri("rsync://example.net/repo/ta/ca/publication-point/"), + Some("rsync://example.net/repo/".to_string()) + ); + assert_eq!( + rsync_module_root_uri("rsync://example.net/repo/ta/"), + Some("rsync://example.net/repo/".to_string()) + ); + assert_eq!( + rsync_module_root_uri("rsync://example.net/repo/"), + Some("rsync://example.net/repo/".to_string()) + ); + assert_eq!(rsync_module_root_uri("https://example.net/repo"), None); + } + + #[test] + fn rsync_host_scope_uri_returns_host_only() { + assert_eq!( + rsync_host_scope_uri("rsync://example.net/repo/ta/ca/publication-point/"), + Some("rsync://example.net/".to_string()) + ); + assert_eq!(rsync_host_scope_uri("https://example.net/repo"), None); + } + + #[test] + fn system_rsync_dedup_key_uses_module_root_by_default() { + let fetcher = SystemRsyncFetcher::new(SystemRsyncConfig::default()); + assert_eq!( + fetcher.dedup_key("rsync://example.net/repo/ta/ca/publication-point/"), + "rsync://example.net/repo/" + ); + assert_eq!( + fetcher.failure_dedup_key("rsync://example.net/repo/ta/ca/publication-point/"), + None + ); + } + + #[test] + fn system_rsync_host_scope_does_not_widen_success_fetch_scope() { + let fetcher = SystemRsyncFetcher::new(SystemRsyncConfig { + scope_policy: RsyncScopePolicy::Host, + ..SystemRsyncConfig::default() + }); + assert_eq!( + fetcher.dedup_key("rsync://example.net/repo/ta/ca/publication-point/"), + "rsync://example.net/repo/ta/ca/publication-point/" + ); + assert_eq!( + fetcher.scope_fetch_uri("rsync://example.net/repo/ta/ca/publication-point/"), + "rsync://example.net/repo/ta/ca/publication-point/" + ); + assert_eq!( + fetcher.failure_dedup_key("rsync://example.net/repo/ta/ca/publication-point/"), + Some("rsync://example.net/".to_string()) + ); + } + + #[test] + fn system_rsync_dedup_key_uses_publication_point_scope_when_configured() { + let fetcher = SystemRsyncFetcher::new(SystemRsyncConfig { + scope_policy: RsyncScopePolicy::PublicationPoint, + ..SystemRsyncConfig::default() + }); + assert_eq!( + fetcher.dedup_key("rsync://example.net/repo/ta/ca/publication-point/"), + "rsync://example.net/repo/ta/ca/publication-point/" + ); + } + + #[test] + fn system_rsync_dedup_key_uses_module_root_when_configured() { + let fetcher = SystemRsyncFetcher::new(SystemRsyncConfig { + scope_policy: RsyncScopePolicy::ModuleRoot, + ..SystemRsyncConfig::default() + }); + assert_eq!( + fetcher.dedup_key("rsync://example.net/repo/ta/ca/publication-point/"), + "rsync://example.net/repo/" + ); + } + + #[test] + fn system_rsync_fetcher_reports_spawn_and_exit_errors() { + let dst = tempfile::tempdir().expect("tempdir"); + + // 1) Spawn error. + let f = SystemRsyncFetcher::new(SystemRsyncConfig { + rsync_bin: PathBuf::from("/this/does/not/exist/rsync"), + connect_timeout: Duration::from_secs(1), + timeout: Duration::from_secs(1), + extra_args: Vec::new(), + mirror_root: None, + scope_policy: RsyncScopePolicy::default(), + }); + let e = f + .run_rsync("rsync://example.net/repo/", dst.path()) + .expect_err("spawn must fail"); + assert!(e.contains("rsync spawn failed:"), "{e}"); + + // 2) Non-zero exit status. + let f = SystemRsyncFetcher::new(SystemRsyncConfig { + rsync_bin: PathBuf::from("false"), + connect_timeout: Duration::from_secs(1), + timeout: Duration::from_secs(1), + extra_args: Vec::new(), + mirror_root: None, + scope_policy: RsyncScopePolicy::default(), + }); + let e = f + .run_rsync("rsync://example.net/repo/", dst.path()) + .expect_err("false must fail"); + assert!(e.contains("rsync failed:"), "{e}"); + assert!(e.contains("status="), "{e}"); + } + + #[test] + fn mirror_dst_dir_reports_root_creation_error() { + let temp = tempfile::tempdir().expect("tempdir"); + let root_file = temp.path().join("mirror-root-file"); + std::fs::write(&root_file, b"not a directory").expect("write root file"); + + let fetcher = SystemRsyncFetcher::new(SystemRsyncConfig { + rsync_bin: PathBuf::from("rsync"), + connect_timeout: Duration::from_secs(1), + timeout: Duration::from_secs(1), + extra_args: Vec::new(), + mirror_root: Some(root_file.clone()), + scope_policy: RsyncScopePolicy::default(), + }); + + let err = fetcher + .mirror_dst_dir("rsync://example.net/repo/") + .expect_err("file mirror root must fail"); + assert!(err.contains("create rsync mirror root failed"), "{err}"); + assert!(err.contains(&root_file.display().to_string()), "{err}"); + } + + #[cfg(unix)] + #[test] + fn mirror_dst_dir_reports_directory_creation_error_inside_root() { + use std::os::unix::fs::PermissionsExt; + + let temp = tempfile::tempdir().expect("tempdir"); + let root = temp.path().join("mirror"); + std::fs::create_dir_all(&root).expect("mkdir root"); + let mut perms = std::fs::metadata(&root).expect("metadata").permissions(); + perms.set_mode(0o555); + std::fs::set_permissions(&root, perms).expect("chmod root readonly"); + + let fetcher = SystemRsyncFetcher::new(SystemRsyncConfig { + rsync_bin: PathBuf::from("rsync"), + connect_timeout: Duration::from_secs(1), + timeout: Duration::from_secs(1), + extra_args: Vec::new(), + mirror_root: Some(root.clone()), + scope_policy: RsyncScopePolicy::default(), + }); + + let err = fetcher + .mirror_dst_dir("rsync://example.net/repo/") + .expect_err("readonly mirror root must fail"); + assert!( + err.contains("create rsync mirror directory failed"), + "{err}" + ); + + let mut perms = std::fs::metadata(&root).expect("metadata").permissions(); + perms.set_mode(0o755); + std::fs::set_permissions(&root, perms).expect("restore perms"); + } + + #[cfg(unix)] + #[test] + fn walk_dir_collect_ignores_non_file_entries() { + use std::os::unix::net::UnixListener; + + let temp = tempfile::tempdir().expect("tempdir"); + let root = temp.path(); + std::fs::write(root.join("a.cer"), b"x").expect("write file"); + let socket_path = root.join("skip.sock"); + let _listener = UnixListener::bind(&socket_path).expect("bind socket"); + + let mut out: Vec<(String, Vec)> = Vec::new(); + walk_dir_visit( + root, + root, + "rsync://example.net/repo/", + &mut |uri, bytes| { + out.push((uri, bytes)); + Ok(()) + }, + ) + .expect("walk"); + + assert_eq!(out.len(), 1); + assert_eq!(out[0].0, "rsync://example.net/repo/a.cer"); + } + + #[cfg(unix)] + #[test] + fn rsync_fail_fast_retries_when_progress_is_made() { + use std::os::unix::fs::PermissionsExt; + + let temp = tempfile::tempdir().expect("tempdir"); + let script = temp.path().join("fake-rsync.sh"); + let state = temp.path().join("state.txt"); + std::fs::write( + &script, + format!( + "#!/usr/bin/env bash\nset -euo pipefail\nSTATE=\"{}\"\nDST=\"${{@: -1}}\"\nCOUNT=0\nif [[ -f \"$STATE\" ]]; then COUNT=$(cat \"$STATE\"); fi\nCOUNT=$((COUNT+1))\necho \"$COUNT\" > \"$STATE\"\nmkdir -p \"$DST\"\nif [[ \"$COUNT\" -eq 1 ]]; then\n echo first > \"$DST/part1\"\n sleep 2\nelse\n echo second > \"$DST/part2\"\nfi\n", + state.display() + ), + ) + .expect("write script"); + let mut perms = std::fs::metadata(&script).unwrap().permissions(); + perms.set_mode(0o755); + std::fs::set_permissions(&script, perms).unwrap(); + + let dst = temp.path().join("dst"); + let fetcher = SystemRsyncFetcher::new(SystemRsyncConfig { + rsync_bin: script, + connect_timeout: Duration::from_secs(15), + timeout: Duration::from_secs(60), + extra_args: Vec::new(), + mirror_root: None, + scope_policy: RsyncScopePolicy::default(), + }); + + fetcher + .run_rsync_fail_fast( + "rsync://example.net/repo/", + &dst, + RsyncFailFastProfile { + initial_wall_clock_timeout: Duration::from_secs(1), + max_wall_clock_timeout: Duration::from_secs(4), + max_attempts: 3, + }, + ) + .expect("eventual success"); + + assert!(dst.join("part1").exists()); + assert!(dst.join("part2").exists()); + } + + #[cfg(unix)] + #[test] + fn rsync_fail_fast_gives_up_after_two_zero_progress_timeouts() { + use std::os::unix::fs::PermissionsExt; + + let temp = tempfile::tempdir().expect("tempdir"); + let script = temp.path().join("fake-rsync.sh"); + std::fs::write(&script, "#!/usr/bin/env bash\nset -euo pipefail\nsleep 5\n") + .expect("write script"); + let mut perms = std::fs::metadata(&script).unwrap().permissions(); + perms.set_mode(0o755); + std::fs::set_permissions(&script, perms).unwrap(); + + let dst = temp.path().join("dst"); + let fetcher = SystemRsyncFetcher::new(SystemRsyncConfig { + rsync_bin: script, + connect_timeout: Duration::from_secs(15), + timeout: Duration::from_secs(60), + extra_args: Vec::new(), + mirror_root: None, + scope_policy: RsyncScopePolicy::default(), + }); + + let err = fetcher + .run_rsync_fail_fast( + "rsync://example.net/repo/", + &dst, + RsyncFailFastProfile { + initial_wall_clock_timeout: Duration::from_secs(1), + max_wall_clock_timeout: Duration::from_secs(2), + max_attempts: 4, + }, + ) + .expect_err("must fail"); + assert!(err.contains("no progress"), "{err}"); + } + + #[cfg(unix)] + #[test] + fn rsync_fail_fast_hard_fail_stops_after_first_attempt() { + use std::os::unix::fs::PermissionsExt; + + let temp = tempfile::tempdir().expect("tempdir"); + let script = temp.path().join("fake-rsync.sh"); + let state = temp.path().join("state.txt"); + std::fs::write( + &script, + format!( + "#!/usr/bin/env bash\nset -euo pipefail\nSTATE=\"{}\"\nCOUNT=0\nif [[ -f \"$STATE\" ]]; then COUNT=$(cat \"$STATE\"); fi\nCOUNT=$((COUNT+1))\necho \"$COUNT\" > \"$STATE\"\necho 'rsync: [Receiver] failed to connect to host (1.2.3.4): Connection refused (111)' >&2\nexit 10\n", + state.display() + ), + ) + .expect("write script"); + let mut perms = std::fs::metadata(&script).unwrap().permissions(); + perms.set_mode(0o755); + std::fs::set_permissions(&script, perms).unwrap(); + + let dst = temp.path().join("dst"); + let fetcher = SystemRsyncFetcher::new(SystemRsyncConfig { + rsync_bin: script, + connect_timeout: Duration::from_secs(15), + timeout: Duration::from_secs(60), + extra_args: Vec::new(), + mirror_root: None, + scope_policy: RsyncScopePolicy::default(), + }); + + let err = fetcher + .run_rsync_fail_fast( + "rsync://example.net/repo/", + &dst, + RsyncFailFastProfile { + initial_wall_clock_timeout: Duration::from_secs(10), + max_wall_clock_timeout: Duration::from_secs(80), + max_attempts: 4, + }, + ) + .expect_err("must hard fail"); + assert!(err.contains("hard-fail"), "{err}"); + let count = std::fs::read_to_string(&state).unwrap(); + assert_eq!(count.trim(), "1"); + } + + #[cfg(unix)] + #[test] + fn run_rsync_once_passes_contimeout_and_timeout_args() { + use std::os::unix::fs::PermissionsExt; + + let temp = tempfile::tempdir().expect("tempdir"); + let script = temp.path().join("capture-rsync.sh"); + let args_file = temp.path().join("args.txt"); + std::fs::write( + &script, + format!( + "#!/usr/bin/env bash\nset -euo pipefail\nprintf '%s\\n' \"$@\" > \"{}\"\nDST=\"${{@: -1}}\"\nmkdir -p \"$DST\"\n", + args_file.display() + ), + ) + .expect("write script"); + let mut perms = std::fs::metadata(&script).unwrap().permissions(); + perms.set_mode(0o755); + std::fs::set_permissions(&script, perms).unwrap(); + + let dst = temp.path().join("dst"); + let fetcher = SystemRsyncFetcher::new(SystemRsyncConfig { + rsync_bin: script, + connect_timeout: Duration::from_secs(15), + timeout: Duration::from_secs(30), + extra_args: Vec::new(), + mirror_root: None, + scope_policy: RsyncScopePolicy::default(), + }); + + fetcher + .run_rsync_once("rsync://example.net/repo/", &dst, None, false) + .expect("rsync"); + + let args = std::fs::read_to_string(&args_file).expect("read args"); + assert!(args.contains("--contimeout\n15\n"), "{args}"); + assert!(args.contains("--timeout\n30\n"), "{args}"); + } +} diff --git a/src/repository/mod.rs b/src/repository/mod.rs new file mode 100644 index 0000000..ab3801b --- /dev/null +++ b/src/repository/mod.rs @@ -0,0 +1,6 @@ +//! Repository transport, protocol state, and object storage. +pub mod blob_store; +pub mod current_repo_index; +pub mod fetch; +pub mod storage; +pub mod sync; diff --git a/src/repository/storage.rs b/src/repository/storage.rs new file mode 100644 index 0000000..d9012dc --- /dev/null +++ b/src/repository/storage.rs @@ -0,0 +1,43 @@ +mod config; +mod keys; +mod memory; +mod pack; +use std::collections::HashSet; +use std::path::Path; + +use rocksdb::checkpoint::Checkpoint; +use rocksdb::{ColumnFamily, DB, Direction, IteratorMode, Options, WriteBatch}; +use serde::{Deserialize, Serialize}; + +use crate::repository::blob_store::{ExternalRawStoreDb, ExternalRepoBytesDb, RawObjectStore}; + +use config::*; +pub use config::{ + ALL_COLUMN_FAMILY_NAMES, CF_MANIFEST_ANTI_ROLLBACK, CF_RAW_BY_HASH, CF_REPOSITORY_VIEW, + CF_RRDP_SOURCE, CF_RRDP_SOURCE_MEMBER, CF_RRDP_URI_OWNER, column_family_descriptors, +}; +use keys::*; +pub(crate) use memory::memory_db_snapshot_for_column_families; +pub use memory::{ + RocksDbColumnFamilyMemoryProperties, RocksDbMemoryDbSnapshot, RocksDbMemoryProperties, + RocksDbMemorySnapshot, RocksDbMemoryTotals, +}; +use pack::compute_sha256_32; +pub use pack::{PackBytes, PackFile, PackTime}; + +pub struct RocksStore { + db: DB, + external_raw_store: Option, + external_repo_bytes: Option, +} +include!("storage/models_core.rs"); +include!("storage/models_summary.rs"); +include!("storage/store_lifecycle.rs"); +include!("storage/store_repository.rs"); +include!("storage/store_manifest.rs"); +include!("storage/store_transport_rrdp.rs"); +include!("storage/verification.rs"); + +#[cfg(test)] +#[path = "storage/tests.rs"] +mod tests; diff --git a/src/repository/storage/config.rs b/src/repository/storage/config.rs new file mode 100644 index 0000000..db0034b --- /dev/null +++ b/src/repository/storage/config.rs @@ -0,0 +1,43 @@ +use rocksdb::{ColumnFamilyDescriptor, DBCompressionType, Options}; + +pub const CF_REPOSITORY_VIEW: &str = "repository_view"; +pub const CF_RAW_BY_HASH: &str = "raw_by_hash"; +pub const CF_RAW_BLOB: &str = "raw_blob"; +/// Persistent RFC 9286 manifest freshness metadata. This is protocol state, +/// not a validation-result cache. +pub const CF_MANIFEST_ANTI_ROLLBACK: &str = "manifest_anti_rollback"; +pub const CF_RRDP_SOURCE: &str = "rrdp_source"; +pub const CF_RRDP_SOURCE_MEMBER: &str = "rrdp_source_member"; +pub const CF_RRDP_URI_OWNER: &str = "rrdp_uri_owner"; + +pub const ALL_COLUMN_FAMILY_NAMES: &[&str] = &[ + CF_REPOSITORY_VIEW, + CF_RAW_BY_HASH, + CF_RAW_BLOB, + CF_MANIFEST_ANTI_ROLLBACK, + CF_RRDP_SOURCE, + CF_RRDP_SOURCE_MEMBER, + CF_RRDP_URI_OWNER, +]; + +pub(super) const REPOSITORY_VIEW_KEY_PREFIX: &str = "repo_view:"; +pub(super) const RAW_BY_HASH_KEY_PREFIX: &str = "rawbyhash:"; +pub(super) const RAW_BLOB_KEY_PREFIX: &str = "rawblob:"; +pub(super) const MANIFEST_ANTI_ROLLBACK_KEY_PREFIX: &str = "manifest_anti_rollback:"; +pub(super) const RRDP_SOURCE_KEY_PREFIX: &str = "rrdp_source:"; +pub(super) const RRDP_SOURCE_MEMBER_KEY_PREFIX: &str = "rrdp_source_member:"; +pub(super) const RRDP_URI_OWNER_KEY_PREFIX: &str = "rrdp_uri_owner:"; + +pub(super) fn configure_work_db_options(opts: &mut Options) { + opts.set_compression_type(DBCompressionType::Lz4); +} +pub fn column_family_descriptors() -> Vec { + ALL_COLUMN_FAMILY_NAMES + .iter() + .map(|name| { + let mut opts = Options::default(); + configure_work_db_options(&mut opts); + ColumnFamilyDescriptor::new(*name, opts) + }) + .collect() +} diff --git a/src/repository/storage/keys.rs b/src/repository/storage/keys.rs new file mode 100644 index 0000000..8012cb4 --- /dev/null +++ b/src/repository/storage/keys.rs @@ -0,0 +1,130 @@ +use serde::{Serialize, de::DeserializeOwned}; + +use super::config::*; +use super::pack::PackTime; +use super::{StorageError, StorageResult}; + +pub(super) fn repository_view_key(rsync_uri: &str) -> String { + format!("{REPOSITORY_VIEW_KEY_PREFIX}{rsync_uri}") +} + +pub(super) fn repository_view_prefix(rsync_uri_prefix: &str) -> String { + format!("{REPOSITORY_VIEW_KEY_PREFIX}{rsync_uri_prefix}") +} + +pub(super) fn raw_by_hash_key(sha256_hex: &str) -> String { + format!("{RAW_BY_HASH_KEY_PREFIX}{sha256_hex}") +} + +pub(super) fn raw_blob_key(sha256_hex: &str) -> String { + format!("{RAW_BLOB_KEY_PREFIX}{sha256_hex}") +} + +pub(super) fn manifest_anti_rollback_key(manifest_rsync_uri: &str) -> String { + format!("{MANIFEST_ANTI_ROLLBACK_KEY_PREFIX}{manifest_rsync_uri}") +} + +pub(super) fn rrdp_source_key(notify_uri: &str) -> String { + format!("{RRDP_SOURCE_KEY_PREFIX}{notify_uri}") +} + +pub(super) fn rrdp_source_member_key(notify_uri: &str, rsync_uri: &str) -> String { + format!("{RRDP_SOURCE_MEMBER_KEY_PREFIX}{notify_uri}:{rsync_uri}") +} + +pub(super) fn rrdp_source_member_prefix(notify_uri: &str) -> String { + format!("{RRDP_SOURCE_MEMBER_KEY_PREFIX}{notify_uri}:") +} + +pub(super) fn rrdp_uri_owner_key(rsync_uri: &str) -> String { + format!("{RRDP_URI_OWNER_KEY_PREFIX}{rsync_uri}") +} + +pub(super) fn encode_cbor(value: &T, entity: &'static str) -> StorageResult> { + serde_cbor::to_vec(value).map_err(|e| StorageError::Codec { + entity, + detail: e.to_string(), + }) +} + +pub(super) fn decode_cbor( + bytes: &[u8], + entity: &'static str, +) -> StorageResult { + serde_cbor::from_slice(bytes).map_err(|e| StorageError::Codec { + entity, + detail: e.to_string(), + }) +} + +pub(super) fn validate_non_empty(field: &'static str, value: &str) -> StorageResult<()> { + if value.is_empty() { + return Err(StorageError::InvalidData { + entity: field, + detail: "must not be empty".to_string(), + }); + } + Ok(()) +} + +pub(super) fn validate_sha256_hex(field: &'static str, value: &str) -> StorageResult<()> { + if value.len() != 64 || !value.as_bytes().iter().all(u8::is_ascii_hexdigit) { + return Err(StorageError::InvalidData { + entity: field, + detail: "must be a 64-character lowercase or uppercase SHA-256 hex string".to_string(), + }); + } + Ok(()) +} + +pub(super) fn decode_sha256_hex_32(field: &'static str, value: &str) -> StorageResult<[u8; 32]> { + validate_sha256_hex(field, value)?; + let mut out = [0u8; 32]; + hex::decode_to_slice(value, &mut out).map_err(|e| StorageError::InvalidData { + entity: field, + detail: format!("hex decode failed: {e}"), + })?; + Ok(out) +} + +pub(super) fn validate_manifest_number_be(field: &'static str, value: &[u8]) -> StorageResult<()> { + if value.is_empty() { + return Err(StorageError::InvalidData { + entity: field, + detail: "must not be empty".to_string(), + }); + } + if value.len() > 20 { + return Err(StorageError::InvalidData { + entity: field, + detail: "must be at most 20 octets".to_string(), + }); + } + if value.len() > 1 && value[0] == 0 { + return Err(StorageError::InvalidData { + entity: field, + detail: "must be minimal big-endian without leading zeros".to_string(), + }); + } + Ok(()) +} + +pub(super) fn validate_sha256_digest_bytes(field: &'static str, value: &[u8]) -> StorageResult<()> { + if value.len() != 32 { + return Err(StorageError::InvalidData { + entity: field, + detail: format!("must be 32 bytes, got {}", value.len()), + }); + } + Ok(()) +} + +pub(super) fn parse_time( + field: &'static str, + value: &PackTime, +) -> StorageResult { + value.parse().map_err(|detail| StorageError::InvalidData { + entity: field, + detail, + }) +} diff --git a/src/repository/storage/memory.rs b/src/repository/storage/memory.rs new file mode 100644 index 0000000..953192e --- /dev/null +++ b/src/repository/storage/memory.rs @@ -0,0 +1,191 @@ +//! RocksDB and process-memory observation. +//! +//! This module owns the serialization-friendly memory snapshot model and the +//! mapping from RocksDB property names to that model. Storage mutation stays +//! in the parent module. + +use rocksdb::DB; +use serde::Serialize; + +const ROCKSDB_MEMORY_PROPERTY_NAMES: &[(&str, &str)] = &[ + ("cur_size_all_mem_tables", "rocksdb.cur-size-all-mem-tables"), + ("size_all_mem_tables", "rocksdb.size-all-mem-tables"), + ( + "estimate_table_readers_mem", + "rocksdb.estimate-table-readers-mem", + ), + ("block_cache_capacity", "rocksdb.block-cache-capacity"), + ("block_cache_usage", "rocksdb.block-cache-usage"), + ( + "block_cache_pinned_usage", + "rocksdb.block-cache-pinned-usage", + ), + ("num_snapshots", "rocksdb.num-snapshots"), + ("background_errors", "rocksdb.background-errors"), +]; + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] +pub struct RocksDbMemoryProperties { + pub cur_size_all_mem_tables: Option, + pub size_all_mem_tables: Option, + pub estimate_table_readers_mem: Option, + pub block_cache_capacity: Option, + pub block_cache_usage: Option, + pub block_cache_pinned_usage: Option, + pub num_snapshots: Option, + pub background_errors: Option, + pub errors: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct RocksDbColumnFamilyMemoryProperties { + pub name: String, + pub properties: RocksDbMemoryProperties, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct RocksDbMemoryDbSnapshot { + pub label: String, + pub properties: RocksDbMemoryProperties, + pub column_families: Vec, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] +pub struct RocksDbMemoryTotals { + pub cur_size_all_mem_tables: u64, + pub size_all_mem_tables: u64, + pub estimate_table_readers_mem: u64, + pub block_cache_capacity: u64, + pub block_cache_usage: u64, + pub block_cache_pinned_usage: u64, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct RocksDbMemorySnapshot { + pub databases: Vec, + pub totals: RocksDbMemoryTotals, +} + +impl RocksDbMemoryTotals { + pub(super) fn add_properties(&mut self, properties: &RocksDbMemoryProperties) { + self.cur_size_all_mem_tables += properties.cur_size_all_mem_tables.unwrap_or(0); + self.size_all_mem_tables += properties.size_all_mem_tables.unwrap_or(0); + self.estimate_table_readers_mem += properties.estimate_table_readers_mem.unwrap_or(0); + self.block_cache_capacity += properties.block_cache_capacity.unwrap_or(0); + self.block_cache_usage += properties.block_cache_usage.unwrap_or(0); + self.block_cache_pinned_usage += properties.block_cache_pinned_usage.unwrap_or(0); + } +} + +fn set_memory_property(properties: &mut RocksDbMemoryProperties, name: &str, value: u64) { + match name { + "cur_size_all_mem_tables" => properties.cur_size_all_mem_tables = Some(value), + "size_all_mem_tables" => properties.size_all_mem_tables = Some(value), + "estimate_table_readers_mem" => properties.estimate_table_readers_mem = Some(value), + "block_cache_capacity" => properties.block_cache_capacity = Some(value), + "block_cache_usage" => properties.block_cache_usage = Some(value), + "block_cache_pinned_usage" => properties.block_cache_pinned_usage = Some(value), + "num_snapshots" => properties.num_snapshots = Some(value), + "background_errors" => properties.background_errors = Some(value), + _ => {} + } +} + +fn parse_rocksdb_property_int(raw: Option) -> Option { + raw.and_then(|value| value.trim().parse::().ok()) +} + +fn memory_properties_for_db(db: &DB) -> RocksDbMemoryProperties { + let mut properties = RocksDbMemoryProperties::default(); + for (field_name, property_name) in ROCKSDB_MEMORY_PROPERTY_NAMES { + match db.property_value(*property_name) { + Ok(value) => { + if let Some(parsed) = parse_rocksdb_property_int(value) { + set_memory_property(&mut properties, field_name, parsed); + } + } + Err(err) => properties.errors.push(format!("{property_name}: {}", err)), + } + } + properties +} + +fn memory_properties_for_cf(db: &DB, cf_name: &'static str) -> RocksDbColumnFamilyMemoryProperties { + let mut properties = RocksDbMemoryProperties::default(); + let Some(cf) = db.cf_handle(cf_name) else { + properties + .errors + .push(format!("missing column family: {cf_name}")); + return RocksDbColumnFamilyMemoryProperties { + name: cf_name.to_string(), + properties, + }; + }; + for (field_name, property_name) in ROCKSDB_MEMORY_PROPERTY_NAMES { + match db.property_value_cf(cf, *property_name) { + Ok(value) => { + if let Some(parsed) = parse_rocksdb_property_int(value) { + set_memory_property(&mut properties, field_name, parsed); + } + } + Err(err) => properties.errors.push(format!("{property_name}: {}", err)), + } + } + RocksDbColumnFamilyMemoryProperties { + name: cf_name.to_string(), + properties, + } +} + +pub(crate) fn memory_db_snapshot_for_column_families( + label: impl Into, + db: &DB, + column_families: Option<&[&'static str]>, +) -> RocksDbMemoryDbSnapshot { + RocksDbMemoryDbSnapshot { + label: label.into(), + properties: memory_properties_for_db(db), + column_families: column_families + .map(|names| { + names + .iter() + .map(|name| memory_properties_for_cf(db, name)) + .collect() + }) + .unwrap_or_default(), + } +} + +#[cfg(test)] +mod tests { + use super::{ + RocksDbMemoryProperties, RocksDbMemoryTotals, parse_rocksdb_property_int, + set_memory_property, + }; + + #[test] + fn parses_rocksdb_integer_properties_without_panicking() { + assert_eq!( + parse_rocksdb_property_int(Some(" 42 ".to_string())), + Some(42) + ); + assert_eq!( + parse_rocksdb_property_int(Some("not-a-number".to_string())), + None + ); + assert_eq!(parse_rocksdb_property_int(None), None); + } + + #[test] + fn aggregates_only_known_memory_properties() { + let mut properties = RocksDbMemoryProperties::default(); + set_memory_property(&mut properties, "block_cache_usage", 11); + set_memory_property(&mut properties, "unknown", 99); + + let mut totals = RocksDbMemoryTotals::default(); + totals.add_properties(&properties); + + assert_eq!(totals.block_cache_usage, 11); + assert_eq!(totals.block_cache_capacity, 0); + } +} diff --git a/src/repository/storage/models_core.rs b/src/repository/storage/models_core.rs new file mode 100644 index 0000000..c95bcb2 --- /dev/null +++ b/src/repository/storage/models_core.rs @@ -0,0 +1,153 @@ +// Repository view, raw-object records, and protocol rollback metadata. + +#[derive(Debug, thiserror::Error)] +pub enum StorageError { + #[error("rocksdb error: {0}")] + RocksDb(String), + #[error("missing column family: {0}")] + MissingColumnFamily(&'static str), + #[error("cbor codec error for {entity}: {detail}")] + Codec { entity: &'static str, detail: String }, + #[error("invalid {entity}: {detail}")] + InvalidData { entity: &'static str, detail: String }, +} + +pub type StorageResult = Result; + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] +pub struct RepositoryBlobVerificationSummary { + pub current_objects: u64, + pub bytes_verified: u64, + pub batches: u64, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum RrdpDeltaOp { + Upsert { rsync_uri: String, bytes: Vec }, + Delete { rsync_uri: String }, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum RepositoryViewState { + Present, + Withdrawn, + Replaced, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct RepositoryViewEntry { + pub rsync_uri: String, + pub current_hash: Option, + pub repository_source: Option, + pub object_type: Option, + pub state: RepositoryViewState, +} + +impl RepositoryViewEntry { + pub fn validate_internal(&self) -> StorageResult<()> { + validate_non_empty("repository_view.rsync_uri", &self.rsync_uri)?; + if let Some(source) = &self.repository_source { + validate_non_empty("repository_view.repository_source", source)?; + } + match self.state { + RepositoryViewState::Present | RepositoryViewState::Replaced => { + let hash = self.current_hash.as_deref().ok_or(StorageError::InvalidData { + entity: "repository_view", + detail: "current_hash is required when state is present or replaced".to_string(), + })?; + validate_sha256_hex("repository_view.current_hash", hash)?; + } + RepositoryViewState::Withdrawn => { + if let Some(hash) = &self.current_hash { + validate_sha256_hex("repository_view.current_hash", hash)?; + } + } + } + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct RawByHashEntry { + pub sha256_hex: String, + pub bytes: Vec, + pub origin_uris: Vec, + pub object_type: Option, + pub encoding: Option, +} + +impl RawByHashEntry { + pub fn from_bytes(sha256_hex: impl Into, bytes: Vec) -> Self { + Self { sha256_hex: sha256_hex.into(), bytes, origin_uris: Vec::new(), object_type: None, encoding: None } + } + + pub fn validate_internal(&self) -> StorageResult<()> { + validate_sha256_hex("raw_by_hash.sha256_hex", &self.sha256_hex)?; + if self.bytes.is_empty() { + return Err(StorageError::InvalidData { entity: "raw_by_hash", detail: "bytes must not be empty".to_string() }); + } + let computed = hex::encode(compute_sha256_32(&self.bytes)); + if computed != self.sha256_hex.to_ascii_lowercase() { + return Err(StorageError::InvalidData { entity: "raw_by_hash", detail: "sha256_hex does not match bytes".to_string() }); + } + let mut seen = HashSet::with_capacity(self.origin_uris.len()); + for uri in &self.origin_uris { + validate_non_empty("raw_by_hash.origin_uris[]", uri)?; + if !seen.insert(uri.as_str()) { + return Err(StorageError::InvalidData { entity: "raw_by_hash", detail: format!("duplicate origin URI: {uri}") }); + } + } + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CurrentObjectWithHash { + pub current_hash_hex: String, + pub current_hash: [u8; 32], + pub bytes: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct ValidatedManifestMeta { + pub validated_manifest_number: Vec, + pub validated_manifest_this_update: PackTime, + pub validated_manifest_next_update: PackTime, +} + +impl ValidatedManifestMeta { + pub fn validate_internal(&self) -> StorageResult<()> { + validate_manifest_number_be("validated_manifest_meta.validated_manifest_number", &self.validated_manifest_number)?; + let this_update = parse_time("validated_manifest_meta.validated_manifest_this_update", &self.validated_manifest_this_update)?; + let next_update = parse_time("validated_manifest_meta.validated_manifest_next_update", &self.validated_manifest_next_update)?; + if next_update < this_update { + return Err(StorageError::InvalidData { entity: "validated_manifest_meta", detail: "validated_manifest_next_update must be >= validated_manifest_this_update".to_string() }); + } + Ok(()) + } +} + +/// Persistent manifest freshness metadata used by RFC 9286 anti-rollback +/// checks. It stores no validated object output. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct ManifestAntiRollbackMeta { + pub manifest_rsync_uri: String, + pub manifest_number_be: Vec, + pub manifest_this_update: PackTime, + pub manifest_sha256: Vec, + pub updated_at_validation_time: PackTime, +} + +impl ManifestAntiRollbackMeta { + pub fn validate_internal(&self) -> StorageResult<()> { + validate_non_empty("manifest_anti_rollback.manifest_rsync_uri", &self.manifest_rsync_uri)?; + validate_manifest_number_be("manifest_anti_rollback.manifest_number_be", &self.manifest_number_be)?; + parse_time("manifest_anti_rollback.manifest_this_update", &self.manifest_this_update)?; + validate_sha256_digest_bytes("manifest_anti_rollback.manifest_sha256", &self.manifest_sha256)?; + parse_time("manifest_anti_rollback.updated_at_validation_time", &self.updated_at_validation_time)?; + Ok(()) + } +} + +pub use crate::ccr::projection::CcrManifestProjection; diff --git a/src/repository/storage/models_summary.rs b/src/repository/storage/models_summary.rs new file mode 100644 index 0000000..357fff4 --- /dev/null +++ b/src/repository/storage/models_summary.rs @@ -0,0 +1,117 @@ +// RRDP protocol state records. + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum RrdpSourceSyncState { + Empty, + SnapshotOnly, + DeltaReady, + Error, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct RrdpSourceRecord { + pub notify_uri: String, + pub last_session_id: Option, + pub last_serial: Option, + pub first_seen_at: PackTime, + pub last_seen_at: PackTime, + pub last_sync_at: Option, + pub sync_state: RrdpSourceSyncState, + pub last_snapshot_uri: Option, + pub last_snapshot_hash: Option, + pub last_error: Option, +} + +impl RrdpSourceRecord { + pub fn validate_internal(&self) -> StorageResult<()> { + validate_non_empty("rrdp_source.notify_uri", &self.notify_uri)?; + if let Some(session_id) = &self.last_session_id { + validate_non_empty("rrdp_source.last_session_id", session_id)?; + } + parse_time("rrdp_source.first_seen_at", &self.first_seen_at)?; + parse_time("rrdp_source.last_seen_at", &self.last_seen_at)?; + if let Some(last_sync_at) = &self.last_sync_at { + parse_time("rrdp_source.last_sync_at", last_sync_at)?; + } + if let Some(last_snapshot_uri) = &self.last_snapshot_uri { + validate_non_empty("rrdp_source.last_snapshot_uri", last_snapshot_uri)?; + } + if let Some(last_snapshot_hash) = &self.last_snapshot_hash { + validate_sha256_hex("rrdp_source.last_snapshot_hash", last_snapshot_hash)?; + } + if let Some(last_error) = &self.last_error { + validate_non_empty("rrdp_source.last_error", last_error)?; + } + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct RrdpSourceMemberRecord { + pub notify_uri: String, + pub rsync_uri: String, + pub current_hash: Option, + pub object_type: Option, + pub present: bool, + pub last_confirmed_session_id: String, + pub last_confirmed_serial: u64, + pub last_changed_at: PackTime, +} + +impl RrdpSourceMemberRecord { + pub fn validate_internal(&self) -> StorageResult<()> { + validate_non_empty("rrdp_source_member.notify_uri", &self.notify_uri)?; + validate_non_empty("rrdp_source_member.rsync_uri", &self.rsync_uri)?; + validate_non_empty( + "rrdp_source_member.last_confirmed_session_id", + &self.last_confirmed_session_id, + )?; + if self.present { + let hash = self.current_hash.as_deref().ok_or(StorageError::InvalidData { + entity: "rrdp_source_member", + detail: "current_hash is required when present=true".to_string(), + })?; + validate_sha256_hex("rrdp_source_member.current_hash", hash)?; + } else if let Some(hash) = &self.current_hash { + validate_sha256_hex("rrdp_source_member.current_hash", hash)?; + } + parse_time("rrdp_source_member.last_changed_at", &self.last_changed_at)?; + Ok(()) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum RrdpUriOwnerState { + Active, + Conflict, + Withdrawn, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct RrdpUriOwnerRecord { + pub rsync_uri: String, + pub notify_uri: String, + pub current_hash: Option, + pub last_confirmed_session_id: String, + pub last_confirmed_serial: u64, + pub last_changed_at: PackTime, + pub owner_state: RrdpUriOwnerState, +} + +impl RrdpUriOwnerRecord { + pub fn validate_internal(&self) -> StorageResult<()> { + validate_non_empty("rrdp_uri_owner.rsync_uri", &self.rsync_uri)?; + validate_non_empty("rrdp_uri_owner.notify_uri", &self.notify_uri)?; + validate_non_empty( + "rrdp_uri_owner.last_confirmed_session_id", + &self.last_confirmed_session_id, + )?; + if let Some(hash) = &self.current_hash { + validate_sha256_hex("rrdp_uri_owner.current_hash", hash)?; + } + parse_time("rrdp_uri_owner.last_changed_at", &self.last_changed_at)?; + Ok(()) + } +} diff --git a/src/repository/storage/pack.rs b/src/repository/storage/pack.rs new file mode 100644 index 0000000..46c61b1 --- /dev/null +++ b/src/repository/storage/pack.rs @@ -0,0 +1,200 @@ +use serde::{Deserialize, Serialize}; +use sha2::Digest; + +use crate::repository::blob_store::{ExternalRawStoreDb, ExternalRepoBytesDb, RawObjectStore}; + +#[derive(Clone, Debug)] +pub enum PackBytes { + Eager(std::sync::Arc<[u8]>), + LazyExternal { + sha256_hex: String, + store: std::sync::Arc, + cache: std::sync::Arc>>, + }, + LazyRepoBytes { + sha256_hex: String, + store: std::sync::Arc, + cache: std::sync::Arc>>, + }, +} + +impl PackBytes { + pub fn eager(bytes: Vec) -> Self { + Self::Eager(std::sync::Arc::from(bytes)) + } + + pub fn lazy_external(sha256_hex: String, store: std::sync::Arc) -> Self { + Self::LazyExternal { + sha256_hex, + store, + cache: std::sync::Arc::new(std::sync::OnceLock::new()), + } + } + + pub fn lazy_repo_bytes(sha256_hex: String, store: std::sync::Arc) -> Self { + Self::LazyRepoBytes { + sha256_hex, + store, + cache: std::sync::Arc::new(std::sync::OnceLock::new()), + } + } + + pub fn as_slice(&self) -> Result<&[u8], String> { + match self { + Self::Eager(bytes) => Ok(bytes.as_ref()), + Self::LazyExternal { + sha256_hex, + store, + cache, + } => { + if cache.get().is_none() { + let bytes = store + .get_blob_bytes(sha256_hex) + .map_err(|e| e.to_string())? + .ok_or_else(|| format!("missing raw blob for sha256={sha256_hex}"))?; + let _ = cache.set(std::sync::Arc::from(bytes)); + } + let bytes = cache + .get() + .ok_or_else(|| format!("missing raw blob cache for sha256={sha256_hex}"))?; + Ok(bytes.as_ref()) + } + Self::LazyRepoBytes { + sha256_hex, + store, + cache, + } => { + if cache.get().is_none() { + let bytes = store + .get_blob_bytes(sha256_hex) + .map_err(|e| e.to_string())? + .ok_or_else(|| format!("missing repo bytes for sha256={sha256_hex}"))?; + let _ = cache.set(std::sync::Arc::from(bytes)); + } + let bytes = cache + .get() + .ok_or_else(|| format!("missing repo bytes cache for sha256={sha256_hex}"))?; + Ok(bytes.as_ref()) + } + } + } + + pub fn to_vec(&self) -> Result, String> { + Ok(self.as_slice()?.to_vec()) + } +} + +impl PartialEq for PackBytes { + fn eq(&self, other: &Self) -> bool { + match (self.as_slice(), other.as_slice()) { + (Ok(a), Ok(b)) => a == b, + _ => false, + } + } +} + +impl Eq for PackBytes {} + +#[derive(Clone, Debug)] +pub struct PackFile { + pub rsync_uri: String, + pub bytes: PackBytes, + pub sha256: [u8; 32], +} + +impl PackFile { + pub fn new(rsync_uri: impl Into, bytes: PackBytes, sha256: [u8; 32]) -> Self { + Self { + rsync_uri: rsync_uri.into(), + bytes, + sha256, + } + } + + pub fn from_bytes_with_sha256( + rsync_uri: impl Into, + bytes: Vec, + sha256: [u8; 32], + ) -> Self { + Self::new(rsync_uri, PackBytes::eager(bytes), sha256) + } + + pub fn from_lazy_external_raw_store( + rsync_uri: impl Into, + sha256_hex: String, + sha256: [u8; 32], + store: std::sync::Arc, + ) -> Self { + Self::new( + rsync_uri, + PackBytes::lazy_external(sha256_hex, store), + sha256, + ) + } + + pub fn from_lazy_repo_bytes( + rsync_uri: impl Into, + sha256_hex: String, + sha256: [u8; 32], + store: std::sync::Arc, + ) -> Self { + Self::new( + rsync_uri, + PackBytes::lazy_repo_bytes(sha256_hex, store), + sha256, + ) + } + + pub fn from_bytes_compute_sha256(rsync_uri: impl Into, bytes: Vec) -> Self { + let sha256 = compute_sha256_32(&bytes); + Self::new(rsync_uri, PackBytes::eager(bytes), sha256) + } + + pub fn bytes(&self) -> Result<&[u8], String> { + self.bytes.as_slice() + } + + pub fn bytes_cloned(&self) -> Result, String> { + self.bytes.to_vec() + } + + pub fn compute_sha256(&self) -> Result<[u8; 32], String> { + Ok(compute_sha256_32(self.bytes()?)) + } +} + +impl PartialEq for PackFile { + fn eq(&self, other: &Self) -> bool { + self.rsync_uri == other.rsync_uri + && self.sha256 == other.sha256 + && self.bytes == other.bytes + } +} + +impl Eq for PackFile {} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct PackTime { + pub rfc3339_utc: String, +} + +impl PackTime { + pub fn from_utc_offset_datetime(t: time::OffsetDateTime) -> Self { + use time::format_description::well_known::Rfc3339; + let utc = t.to_offset(time::UtcOffset::UTC); + let s = utc.format(&Rfc3339).expect("format RFC 3339 UTC time"); + Self { rfc3339_utc: s } + } + + pub fn parse(&self) -> Result { + use time::format_description::well_known::Rfc3339; + time::OffsetDateTime::parse(&self.rfc3339_utc, &Rfc3339).map_err(|e| e.to_string()) + } +} + +pub(super) fn compute_sha256_32(bytes: &[u8]) -> [u8; 32] { + let digest = sha2::Sha256::digest(bytes); + let mut out = [0u8; 32]; + out.copy_from_slice(&digest); + out +} diff --git a/src/repository/storage/store_lifecycle.rs b/src/repository/storage/store_lifecycle.rs new file mode 100644 index 0000000..bb0f6a9 --- /dev/null +++ b/src/repository/storage/store_lifecycle.rs @@ -0,0 +1,158 @@ +// RocksDB lifecycle and external repository stores. + +impl RocksStore { + pub fn create_read_only_checkpoint(source: &Path, destination: &Path) -> StorageResult<()> { + reject_unsupported_column_families(source)?; + if destination.exists() { + return Err(StorageError::InvalidData { + entity: "work_db_checkpoint", + detail: format!( + "checkpoint destination already exists: {}", + destination.display() + ), + }); + } + if let Some(parent) = destination.parent() { + std::fs::create_dir_all(parent) + .map_err(|error| StorageError::RocksDb(error.to_string()))?; + } + let mut options = Options::default(); + configure_work_db_options(&mut options); + let descriptors = column_family_descriptors(); + let db = DB::open_cf_descriptors_read_only( + &options, + source, + descriptors, + false, + ) + .map_err(|error| StorageError::RocksDb(error.to_string()))?; + let checkpoint = + Checkpoint::new(&db).map_err(|error| StorageError::RocksDb(error.to_string()))?; + checkpoint + .create_checkpoint(destination) + .map_err(|error| StorageError::RocksDb(error.to_string())) + } + + pub fn open(path: &Path) -> StorageResult { + reject_unsupported_column_families(path)?; + let mut base_opts = Options::default(); + base_opts.create_if_missing(true); + base_opts.create_missing_column_families(true); + configure_work_db_options(&mut base_opts); + let descriptors = column_family_descriptors(); + + let db = DB::open_cf_descriptors( + &base_opts, + path, + descriptors, + ) + .map_err(|e| StorageError::RocksDb(e.to_string()))?; + + Ok(Self { + db, + external_raw_store: None, + external_repo_bytes: None, + }) + } + + pub fn open_with_external_raw_store(path: &Path, raw_store_path: &Path) -> StorageResult { + Self::open_with_external_stores(path, Some(raw_store_path), None) + } + + pub fn open_with_external_repo_bytes( + path: &Path, + repo_bytes_path: &Path, + ) -> StorageResult { + Self::open_with_external_stores(path, None, Some(repo_bytes_path)) + } + + pub fn open_with_external_repo_bytes_read_only( + path: &Path, + repo_bytes_path: &Path, + ) -> StorageResult { + let mut store = Self::open(path)?; + store.external_repo_bytes = Some(ExternalRepoBytesDb::open_read_only(repo_bytes_path)?); + Ok(store) + } + + pub fn open_with_external_stores( + path: &Path, + raw_store_path: Option<&Path>, + repo_bytes_path: Option<&Path>, + ) -> StorageResult { + let mut store = Self::open(path)?; + if let Some(raw_store_path) = raw_store_path { + store.external_raw_store = Some(ExternalRawStoreDb::open(raw_store_path)?); + } + if let Some(repo_bytes_path) = repo_bytes_path { + store.external_repo_bytes = Some(ExternalRepoBytesDb::open(repo_bytes_path)?); + } + Ok(store) + } + + pub(crate) fn external_raw_store_ref(&self) -> Option<&ExternalRawStoreDb> { + self.external_raw_store.as_ref() + } + + pub(crate) fn external_repo_bytes_ref(&self) -> Option<&ExternalRepoBytesDb> { + self.external_repo_bytes.as_ref() + } + + pub fn memory_snapshot(&self) -> RocksDbMemorySnapshot { + let mut databases = Vec::new(); + databases.push(memory_db_snapshot_for_column_families( + "work-db", + &self.db, + Some(ALL_COLUMN_FAMILY_NAMES), + )); + if let Some(raw_store) = self.external_raw_store.as_ref() { + databases.push(raw_store.memory_snapshot("raw-store.db")); + } + if let Some(repo_bytes) = self.external_repo_bytes.as_ref() { + databases.push(repo_bytes.memory_snapshot("repo-bytes.db")); + } + + let mut totals = RocksDbMemoryTotals::default(); + for db in &databases { + totals.add_properties(&db.properties); + } + RocksDbMemorySnapshot { databases, totals } + } + + fn cf(&self, name: &'static str) -> StorageResult<&ColumnFamily> { + self.db + .cf_handle(name) + .ok_or(StorageError::MissingColumnFamily(name)) + } + +} + +fn reject_unsupported_column_families(path: &Path) -> StorageResult<()> { + // An empty directory is a valid first-run state root. A RocksDB directory + // has CURRENT; inspect its column families before opening so an older + // private schema cannot be silently ignored or partially loaded. + if !path.join("CURRENT").exists() { + return Ok(()); + } + let options = Options::default(); + let existing = DB::list_cf(&options, path) + .map_err(|error| StorageError::RocksDb(format!("inspect state schema: {error}")))?; + let supported: std::collections::HashSet<&str> = std::iter::once("default") + .chain(ALL_COLUMN_FAMILY_NAMES.iter().copied()) + .collect(); + let unsupported: Vec<_> = existing + .iter() + .filter(|name| !supported.contains(name.as_str())) + .cloned() + .collect(); + if unsupported.is_empty() { + return Ok(()); + } + Err(StorageError::InvalidData { + entity: "state_schema", + detail: format!( + "unsupported column families {:?}; create a new state directory instead of reusing this database", + unsupported + ), + }) +} diff --git a/src/repository/storage/store_manifest.rs b/src/repository/storage/store_manifest.rs new file mode 100644 index 0000000..23a6019 --- /dev/null +++ b/src/repository/storage/store_manifest.rs @@ -0,0 +1,33 @@ +// Manifest rollback metadata is protocol state retained independently from +// repository objects and validation outputs. + +impl RocksStore { + pub fn get_manifest_anti_rollback_meta( + &self, + manifest_rsync_uri: &str, + ) -> StorageResult> { + let cf = self.cf(CF_MANIFEST_ANTI_ROLLBACK)?; + let key = manifest_anti_rollback_key(manifest_rsync_uri); + let Some(bytes) = self + .db + .get_cf(cf, key.as_bytes()) + .map_err(|error| StorageError::RocksDb(error.to_string()))? + else { + return Ok(None); + }; + let metadata = decode_cbor::(&bytes, "manifest_anti_rollback")?; + metadata.validate_internal()?; + Ok(Some(metadata)) + } + + pub fn put_manifest_anti_rollback_meta(&self, metadata: &ManifestAntiRollbackMeta) -> StorageResult<()> { + metadata.validate_internal()?; + let cf = self.cf(CF_MANIFEST_ANTI_ROLLBACK)?; + let key = manifest_anti_rollback_key(&metadata.manifest_rsync_uri); + let value = encode_cbor(metadata, "manifest_anti_rollback")?; + self.db + .put_cf(cf, key.as_bytes(), value) + .map_err(|error| StorageError::RocksDb(error.to_string()))?; + Ok(()) + } +} diff --git a/src/repository/storage/store_repository.rs b/src/repository/storage/store_repository.rs new file mode 100644 index 0000000..3a48a67 --- /dev/null +++ b/src/repository/storage/store_repository.rs @@ -0,0 +1,363 @@ +// Repository-view and raw-object/blob storage operations. + +impl RocksStore { + + pub fn put_repository_view_entry(&self, entry: &RepositoryViewEntry) -> StorageResult<()> { + entry.validate_internal()?; + let cf = self.cf(CF_REPOSITORY_VIEW)?; + let key = repository_view_key(&entry.rsync_uri); + let value = encode_cbor(entry, "repository_view")?; + self.db + .put_cf(cf, key.as_bytes(), value) + .map_err(|e| StorageError::RocksDb(e.to_string()))?; + Ok(()) + } + + pub fn get_repository_view_entry( + &self, + rsync_uri: &str, + ) -> StorageResult> { + let cf = self.cf(CF_REPOSITORY_VIEW)?; + let key = repository_view_key(rsync_uri); + let Some(bytes) = self + .db + .get_cf(cf, key.as_bytes()) + .map_err(|e| StorageError::RocksDb(e.to_string()))? + else { + return Ok(None); + }; + let entry = decode_cbor::(&bytes, "repository_view")?; + entry.validate_internal()?; + Ok(Some(entry)) + } + + pub fn delete_repository_view_entry(&self, rsync_uri: &str) -> StorageResult<()> { + let cf = self.cf(CF_REPOSITORY_VIEW)?; + let key = repository_view_key(rsync_uri); + self.db + .delete_cf(cf, key.as_bytes()) + .map_err(|e| StorageError::RocksDb(e.to_string()))?; + Ok(()) + } + + pub fn put_projection_batch( + &self, + repository_view_entries: &[RepositoryViewEntry], + member_records: &[RrdpSourceMemberRecord], + owner_records: &[RrdpUriOwnerRecord], + ) -> StorageResult<()> { + if repository_view_entries.is_empty() + && member_records.is_empty() + && owner_records.is_empty() + { + return Ok(()); + } + + let repo_cf = self.cf(CF_REPOSITORY_VIEW)?; + let member_cf = self.cf(CF_RRDP_SOURCE_MEMBER)?; + let owner_cf = self.cf(CF_RRDP_URI_OWNER)?; + let mut batch = WriteBatch::default(); + + for entry in repository_view_entries { + entry.validate_internal()?; + let key = repository_view_key(&entry.rsync_uri); + let value = encode_cbor(entry, "repository_view")?; + batch.put_cf(repo_cf, key.as_bytes(), value); + } + for record in member_records { + record.validate_internal()?; + let key = rrdp_source_member_key(&record.notify_uri, &record.rsync_uri); + let value = encode_cbor(record, "rrdp_source_member")?; + batch.put_cf(member_cf, key.as_bytes(), value); + } + for record in owner_records { + record.validate_internal()?; + let key = rrdp_uri_owner_key(&record.rsync_uri); + let value = encode_cbor(record, "rrdp_uri_owner")?; + batch.put_cf(owner_cf, key.as_bytes(), value); + } + + self.write_batch(batch) + } + + pub fn list_repository_view_entries_with_prefix( + &self, + rsync_uri_prefix: &str, + ) -> StorageResult> { + let cf = self.cf(CF_REPOSITORY_VIEW)?; + let prefix = repository_view_prefix(rsync_uri_prefix); + let mode = IteratorMode::From(prefix.as_bytes(), Direction::Forward); + self.db + .iterator_cf(cf, mode) + .take_while(|res| match res { + Ok((key, _)) => key.starts_with(prefix.as_bytes()), + Err(_) => false, + }) + .map(|res| { + let (_key, value) = res.map_err(|e| StorageError::RocksDb(e.to_string()))?; + let entry = decode_cbor::(&value, "repository_view")?; + entry.validate_internal()?; + Ok(entry) + }) + .collect() + } + + pub fn verify_current_repository_blobs( + &self, + batch_size: usize, + ) -> StorageResult { + if batch_size == 0 { + return Err(StorageError::InvalidData { + entity: "repository_blob_verification", + detail: "batch_size must be greater than zero".to_string(), + }); + } + let cf = self.cf(CF_REPOSITORY_VIEW)?; + let prefix = repository_view_prefix(""); + let mode = IteratorMode::From(prefix.as_bytes(), Direction::Forward); + let mut summary = RepositoryBlobVerificationSummary::default(); + let mut batch = Vec::with_capacity(batch_size); + for item in self.db.iterator_cf(cf, mode) { + let (key, value) = item.map_err(|error| StorageError::RocksDb(error.to_string()))?; + if !key.starts_with(prefix.as_bytes()) { + break; + } + let entry = decode_cbor::(&value, "repository_view")?; + entry.validate_internal()?; + if !matches!( + entry.state, + RepositoryViewState::Present | RepositoryViewState::Replaced + ) { + continue; + } + let hash = entry + .current_hash + .clone() + .ok_or(StorageError::InvalidData { + entity: "repository_blob_verification", + detail: format!("current hash missing for {}", entry.rsync_uri), + })?; + batch.push((entry.rsync_uri, hash)); + if batch.len() >= batch_size { + verify_repository_blob_batch(self, &batch, &mut summary)?; + batch.clear(); + } + } + if !batch.is_empty() { + verify_repository_blob_batch(self, &batch, &mut summary)?; + } + Ok(summary) + } + + pub fn put_raw_by_hash_entry(&self, entry: &RawByHashEntry) -> StorageResult<()> { + entry.validate_internal()?; + if let Some(raw_store) = self.external_raw_store.as_ref() { + return raw_store.put_raw_entry(entry); + } + let cf = self.cf(CF_RAW_BY_HASH)?; + let key = raw_by_hash_key(&entry.sha256_hex); + let value = encode_cbor(entry, "raw_by_hash")?; + self.db + .put_cf(cf, key.as_bytes(), value) + .map_err(|e| StorageError::RocksDb(e.to_string()))?; + Ok(()) + } + + pub fn put_raw_by_hash_entries_batch(&self, entries: &[RawByHashEntry]) -> StorageResult<()> { + if entries.is_empty() { + return Ok(()); + } + if let Some(raw_store) = self.external_raw_store.as_ref() { + return raw_store.put_raw_entries_batch(entries); + } + + let cf = self.cf(CF_RAW_BY_HASH)?; + let mut batch = WriteBatch::default(); + for entry in entries { + entry.validate_internal()?; + let key = raw_by_hash_key(&entry.sha256_hex); + let value = encode_cbor(entry, "raw_by_hash")?; + batch.put_cf(cf, key.as_bytes(), value); + } + self.write_batch(batch) + } + + pub fn put_raw_by_hash_entries_batch_unchecked( + &self, + entries: &[RawByHashEntry], + ) -> StorageResult<()> { + if entries.is_empty() { + return Ok(()); + } + if let Some(raw_store) = self.external_raw_store.as_ref() { + return raw_store.put_raw_entries_batch(entries); + } + + let cf = self.cf(CF_RAW_BY_HASH)?; + let mut batch = WriteBatch::default(); + for entry in entries { + let key = raw_by_hash_key(&entry.sha256_hex); + let value = encode_cbor(entry, "raw_by_hash")?; + batch.put_cf(cf, key.as_bytes(), value); + } + self.write_batch(batch) + } + + pub fn put_blob_bytes_batch(&self, blobs: &[(String, Vec)]) -> StorageResult<()> { + if blobs.is_empty() { + return Ok(()); + } + if let Some(repo_bytes) = self.external_repo_bytes.as_ref() { + if repo_bytes.is_read_only() { + return repo_bytes.require_existing_blob_bytes_batch(blobs); + } + return repo_bytes.put_blob_bytes_batch(blobs); + } + if let Some(raw_store) = self.external_raw_store.as_ref() { + return raw_store.put_blob_bytes_batch(blobs); + } + let cf = self.cf(CF_RAW_BLOB)?; + let mut batch = WriteBatch::default(); + for (sha256_hex, bytes) in blobs { + validate_sha256_hex("raw_blob.sha256_hex", sha256_hex)?; + if bytes.is_empty() { + return Err(StorageError::InvalidData { + entity: "raw_blob", + detail: "bytes must not be empty".to_string(), + }); + } + let key = raw_blob_key(sha256_hex); + batch.put_cf(cf, key.as_bytes(), bytes.as_slice()); + } + self.write_batch(batch) + } + + pub fn delete_raw_by_hash_entry(&self, sha256_hex: &str) -> StorageResult<()> { + validate_sha256_hex("raw_by_hash.sha256_hex", sha256_hex)?; + if let Some(raw_store) = self.external_raw_store.as_ref() { + return raw_store.delete_raw_entry(sha256_hex); + } + let cf = self.cf(CF_RAW_BY_HASH)?; + let key = raw_by_hash_key(sha256_hex); + self.db + .delete_cf(cf, key.as_bytes()) + .map_err(|e| StorageError::RocksDb(e.to_string()))?; + Ok(()) + } + + pub fn get_raw_by_hash_entry(&self, sha256_hex: &str) -> StorageResult> { + if let Some(raw_store) = self.external_raw_store.as_ref() { + return raw_store.get_raw_entry(sha256_hex); + } + let cf = self.cf(CF_RAW_BY_HASH)?; + let key = raw_by_hash_key(sha256_hex); + let Some(bytes) = self + .db + .get_cf(cf, key.as_bytes()) + .map_err(|e| StorageError::RocksDb(e.to_string()))? + else { + return Ok(None); + }; + let entry = decode_cbor::(&bytes, "raw_by_hash")?; + entry.validate_internal()?; + Ok(Some(entry)) + } + + pub fn get_raw_by_hash_entries_batch( + &self, + sha256_hexes: &[String], + ) -> StorageResult>> { + if sha256_hexes.is_empty() { + return Ok(Vec::new()); + } + if let Some(raw_store) = self.external_raw_store.as_ref() { + return raw_store.get_raw_entries_batch(sha256_hexes); + } + + let cf = self.cf(CF_RAW_BY_HASH)?; + let keys: Vec = sha256_hexes + .iter() + .map(|hash| raw_by_hash_key(hash)) + .collect(); + self.db + .multi_get_cf(keys.iter().map(|key| (cf, key.as_bytes()))) + .into_iter() + .map(|res| { + let maybe = res.map_err(|e| StorageError::RocksDb(e.to_string()))?; + match maybe { + Some(bytes) => { + let entry = decode_cbor::(&bytes, "raw_by_hash")?; + entry.validate_internal()?; + Ok(Some(entry)) + } + None => Ok(None), + } + }) + .collect() + } + + pub fn get_blob_bytes(&self, sha256_hex: &str) -> StorageResult>> { + if let Some(repo_bytes) = self.external_repo_bytes.as_ref() { + return repo_bytes.get_blob_bytes(sha256_hex); + } + if let Some(raw_store) = self.external_raw_store.as_ref() { + return raw_store.get_blob_bytes(sha256_hex); + } + validate_sha256_hex("raw_blob.sha256_hex", sha256_hex)?; + let cf = self.cf(CF_RAW_BLOB)?; + let key = raw_blob_key(sha256_hex); + if let Some(bytes) = self + .db + .get_cf(cf, key.as_bytes()) + .map_err(|e| StorageError::RocksDb(e.to_string()))? + { + return Ok(Some(bytes)); + } + self.get_raw_by_hash_entry(sha256_hex) + .map(|entry| entry.map(|entry| entry.bytes)) + } + + pub fn get_blob_bytes_batch( + &self, + sha256_hexes: &[String], + ) -> StorageResult>>> { + if sha256_hexes.is_empty() { + return Ok(Vec::new()); + } + if let Some(repo_bytes) = self.external_repo_bytes.as_ref() { + return repo_bytes.get_blob_bytes_batch(sha256_hexes); + } + if let Some(raw_store) = self.external_raw_store.as_ref() { + return raw_store.get_blob_bytes_batch(sha256_hexes); + } + + let cf = self.cf(CF_RAW_BLOB)?; + let keys: Vec = sha256_hexes + .iter() + .map(|hash| { + validate_sha256_hex("raw_blob.sha256_hex", hash)?; + Ok::(raw_blob_key(hash)) + }) + .collect::>()?; + let blob_results: Vec>> = self + .db + .multi_get_cf(keys.iter().map(|key| (cf, key.as_bytes()))) + .into_iter() + .map(|res| res.map_err(|e| StorageError::RocksDb(e.to_string()))) + .collect::>()?; + + let mut out = Vec::with_capacity(sha256_hexes.len()); + for (sha256_hex, maybe_blob) in sha256_hexes.iter().zip(blob_results.into_iter()) { + if maybe_blob.is_some() { + out.push(maybe_blob); + } else { + out.push( + self.get_raw_by_hash_entry(sha256_hex)? + .map(|entry| entry.bytes), + ); + } + } + Ok(out) + } + +} diff --git a/src/repository/storage/store_transport_rrdp.rs b/src/repository/storage/store_transport_rrdp.rs new file mode 100644 index 0000000..5ecac53 --- /dev/null +++ b/src/repository/storage/store_transport_rrdp.rs @@ -0,0 +1,108 @@ +// RRDP source/session state and repository-view reads. + +impl RocksStore { + pub fn put_rrdp_source_record(&self, record: &RrdpSourceRecord) -> StorageResult<()> { + record.validate_internal()?; + let cf = self.cf(CF_RRDP_SOURCE)?; + let key = rrdp_source_key(&record.notify_uri); + let value = encode_cbor(record, "rrdp_source")?; + self.db.put_cf(cf, key.as_bytes(), value).map_err(|e| StorageError::RocksDb(e.to_string()))?; + Ok(()) + } + + pub fn get_rrdp_source_record(&self, notify_uri: &str) -> StorageResult> { + let cf = self.cf(CF_RRDP_SOURCE)?; + let key = rrdp_source_key(notify_uri); + let Some(bytes) = self.db.get_cf(cf, key.as_bytes()).map_err(|e| StorageError::RocksDb(e.to_string()))? else { return Ok(None) }; + let record = decode_cbor::(&bytes, "rrdp_source")?; + record.validate_internal()?; + Ok(Some(record)) + } + + pub fn put_rrdp_source_member_record(&self, record: &RrdpSourceMemberRecord) -> StorageResult<()> { + record.validate_internal()?; + let cf = self.cf(CF_RRDP_SOURCE_MEMBER)?; + let key = rrdp_source_member_key(&record.notify_uri, &record.rsync_uri); + let value = encode_cbor(record, "rrdp_source_member")?; + self.db.put_cf(cf, key.as_bytes(), value).map_err(|e| StorageError::RocksDb(e.to_string()))?; + Ok(()) + } + + pub fn get_rrdp_source_member_record(&self, notify_uri: &str, rsync_uri: &str) -> StorageResult> { + let cf = self.cf(CF_RRDP_SOURCE_MEMBER)?; + let key = rrdp_source_member_key(notify_uri, rsync_uri); + let Some(bytes) = self.db.get_cf(cf, key.as_bytes()).map_err(|e| StorageError::RocksDb(e.to_string()))? else { return Ok(None) }; + let record = decode_cbor::(&bytes, "rrdp_source_member")?; + record.validate_internal()?; + Ok(Some(record)) + } + + pub fn list_rrdp_source_member_records(&self, notify_uri: &str) -> StorageResult> { + let cf = self.cf(CF_RRDP_SOURCE_MEMBER)?; + let prefix = rrdp_source_member_prefix(notify_uri); + let mode = IteratorMode::From(prefix.as_bytes(), Direction::Forward); + self.db.iterator_cf(cf, mode).take_while(|res| match res { Ok((key, _)) => key.starts_with(prefix.as_bytes()), Err(_) => false }).map(|res| { + let (_key, value) = res.map_err(|e| StorageError::RocksDb(e.to_string()))?; + let record = decode_cbor::(&value, "rrdp_source_member")?; + record.validate_internal()?; + Ok(record) + }).collect() + } + + pub fn list_current_rrdp_source_members(&self, notify_uri: &str) -> StorageResult> { + let mut records = self.list_rrdp_source_member_records(notify_uri)?; + records.retain(|record| record.present); + records.sort_by(|a, b| a.rsync_uri.cmp(&b.rsync_uri)); + Ok(records) + } + + pub fn is_current_rrdp_source_member(&self, notify_uri: &str, rsync_uri: &str) -> StorageResult { + Ok(matches!(self.get_rrdp_source_member_record(notify_uri, rsync_uri)?, Some(record) if record.present)) + } + + pub fn load_current_object_bytes_by_uri(&self, rsync_uri: &str) -> StorageResult>> { + Ok(self.load_current_object_with_hash_by_uri(rsync_uri)?.map(|object| object.bytes)) + } + + pub fn load_current_object_with_hash_by_uri(&self, rsync_uri: &str) -> StorageResult> { + let Some(view) = self.get_repository_view_entry(rsync_uri)? else { return Ok(None) }; + match view.state { + RepositoryViewState::Withdrawn => Ok(None), + RepositoryViewState::Present | RepositoryViewState::Replaced => { + let hash = view.current_hash.as_deref().ok_or(StorageError::InvalidData { entity: "repository_view", detail: format!("current_hash missing for current object URI: {rsync_uri}") })?; + let bytes = self.get_blob_bytes(hash)?.ok_or(StorageError::InvalidData { entity: "repository_view", detail: format!("blob bytes missing for current object URI: {rsync_uri} (hash={hash})") })?; + Ok(Some(CurrentObjectWithHash { current_hash_hex: hash.to_ascii_lowercase(), current_hash: decode_sha256_hex_32("repository_view.current_hash", hash)?, bytes })) + } + } + } + + pub fn put_rrdp_uri_owner_record(&self, record: &RrdpUriOwnerRecord) -> StorageResult<()> { + record.validate_internal()?; + let cf = self.cf(CF_RRDP_URI_OWNER)?; + let key = rrdp_uri_owner_key(&record.rsync_uri); + let value = encode_cbor(record, "rrdp_uri_owner")?; + self.db.put_cf(cf, key.as_bytes(), value).map_err(|e| StorageError::RocksDb(e.to_string()))?; + Ok(()) + } + + pub fn get_rrdp_uri_owner_record(&self, rsync_uri: &str) -> StorageResult> { + let cf = self.cf(CF_RRDP_URI_OWNER)?; + let key = rrdp_uri_owner_key(rsync_uri); + let Some(bytes) = self.db.get_cf(cf, key.as_bytes()).map_err(|e| StorageError::RocksDb(e.to_string()))? else { return Ok(None) }; + let record = decode_cbor::(&bytes, "rrdp_uri_owner")?; + record.validate_internal()?; + Ok(Some(record)) + } + + pub fn delete_rrdp_uri_owner_record(&self, rsync_uri: &str) -> StorageResult<()> { + let cf = self.cf(CF_RRDP_URI_OWNER)?; + let key = rrdp_uri_owner_key(rsync_uri); + self.db.delete_cf(cf, key.as_bytes()).map_err(|e| StorageError::RocksDb(e.to_string()))?; + Ok(()) + } + + pub fn write_batch(&self, batch: WriteBatch) -> StorageResult<()> { + self.db.write(batch).map_err(|e| StorageError::RocksDb(e.to_string()))?; + Ok(()) + } +} diff --git a/src/repository/storage/tests.rs b/src/repository/storage/tests.rs new file mode 100644 index 0000000..725138e --- /dev/null +++ b/src/repository/storage/tests.rs @@ -0,0 +1,52 @@ +// Storage tests are grouped by the public storage surface they protect. +use super::*; +use rocksdb::{DB, Options}; +use sha2::Digest; + +fn sha256_hex(bytes: &[u8]) -> String { + hex::encode(sha2::Sha256::digest(bytes)) +} + +fn pack_time(seconds: i64) -> PackTime { + PackTime::from_utc_offset_datetime( + time::OffsetDateTime::UNIX_EPOCH + time::Duration::seconds(seconds), + ) +} + +fn sample_repository_view_entry(uri: &str, bytes: &[u8]) -> RepositoryViewEntry { + RepositoryViewEntry { + rsync_uri: uri.to_string(), + current_hash: Some(sha256_hex(bytes)), + repository_source: Some("fixture".to_string()), + object_type: Some("roa".to_string()), + state: RepositoryViewState::Present, + } +} + +fn sample_raw_by_hash_entry(bytes: Vec) -> RawByHashEntry { + RawByHashEntry::from_bytes(sha256_hex(&bytes), bytes) +} + +#[test] +fn opening_an_unsupported_state_schema_fails_without_rewriting() { + let td = tempfile::tempdir().expect("tempdir"); + let mut options = Options::default(); + options.create_if_missing(true); + options.create_missing_column_families(true); + let legacy = DB::open_cf(&options, td.path(), ["legacy_private_state"]) + .expect("create legacy state database"); + drop(legacy); + + match RocksStore::open(td.path()) { + Err(StorageError::InvalidData { + entity: "state_schema", + detail, + }) => assert!(detail.contains("legacy_private_state")), + Err(error) => panic!("unexpected schema error: {error}"), + Ok(_) => panic!("legacy schema must be rejected"), + } +} + +include!("tests_parts/repository.rs"); +include!("tests_parts/rrdp.rs"); +include!("tests_parts/object_loading.rs"); diff --git a/src/repository/storage/tests_parts/object_loading.rs b/src/repository/storage/tests_parts/object_loading.rs new file mode 100644 index 0000000..83c118d --- /dev/null +++ b/src/repository/storage/tests_parts/object_loading.rs @@ -0,0 +1,234 @@ +// Storage test group: object loading. + +#[test] +fn load_current_object_with_hash_by_uri_uses_internal_blob_cf_without_raw_entry() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(td.path()).expect("open rocksdb"); + let rsync_uri = "rsync://example.test/repo/blob-only.roa"; + let bytes = b"blob-only-current-object".to_vec(); + let hash = sha256_hex(&bytes); + + store + .put_blob_bytes_batch(&[(hash.clone(), bytes.clone())]) + .expect("put blob bytes"); + store + .put_repository_view_entry(&RepositoryViewEntry { + rsync_uri: rsync_uri.to_string(), + current_hash: Some(hash.clone()), + repository_source: Some("https://rrdp.example.test/notification.xml".to_string()), + object_type: Some("roa".to_string()), + state: RepositoryViewState::Present, + }) + .expect("put view"); + + let got = store + .load_current_object_with_hash_by_uri(rsync_uri) + .expect("load current object") + .expect("current object exists"); + assert_eq!(got.current_hash_hex, hash); + assert_eq!(got.current_hash, compute_sha256_32(&bytes)); + assert_eq!(got.bytes, bytes); + assert!( + store + .get_raw_by_hash_entry(&got.current_hash_hex) + .expect("get raw entry") + .is_none() + ); +} + +#[test] +fn load_current_object_bytes_by_uri_uses_internal_blob_cf_without_raw_entry() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(td.path()).expect("open rocksdb"); + let rsync_uri = "rsync://example.test/repo/blob-only-bytes.roa"; + let bytes = b"blob-only-current-object-bytes".to_vec(); + let hash = sha256_hex(&bytes); + + store + .put_blob_bytes_batch(&[(hash, bytes.clone())]) + .expect("put blob bytes"); + store + .put_repository_view_entry(&RepositoryViewEntry { + rsync_uri: rsync_uri.to_string(), + current_hash: Some(sha256_hex(&bytes)), + repository_source: Some("https://rrdp.example.test/notification.xml".to_string()), + object_type: Some("roa".to_string()), + state: RepositoryViewState::Present, + }) + .expect("put view"); + + assert_eq!( + store + .load_current_object_bytes_by_uri(rsync_uri) + .expect("load current object bytes"), + Some(bytes) + ); +} + +#[test] +fn pack_file_can_lazy_load_bytes_from_external_raw_store() { + let td = tempfile::tempdir().expect("tempdir"); + let raw_store = std::sync::Arc::new( + ExternalRawStoreDb::open(td.path().join("raw-store.db")).expect("open raw store"), + ); + let bytes = b"lazy-pack-file".to_vec(); + let sha256_hex = sha256_hex(&bytes); + raw_store + .put_raw_entry(&RawByHashEntry::from_bytes( + sha256_hex.clone(), + bytes.clone(), + )) + .expect("put raw entry"); + + let file = PackFile::from_lazy_external_raw_store( + "rsync://example.test/repo/a.roa", + sha256_hex, + compute_sha256_32(&bytes), + raw_store, + ); + + assert_eq!(file.bytes().expect("lazy bytes"), bytes.as_slice()); + assert_eq!(file.bytes_cloned().expect("cloned bytes"), bytes); +} + +#[test] +fn pack_file_can_lazy_load_bytes_from_external_repo_bytes_store() { + let td = tempfile::tempdir().expect("tempdir"); + let repo_bytes_store = std::sync::Arc::new( + ExternalRepoBytesDb::open(td.path().join("repo-bytes.db")).expect("open repo bytes"), + ); + let bytes = b"repo-object-pack-file".to_vec(); + let sha256_hex = sha256_hex(&bytes); + repo_bytes_store + .put_blob_bytes_batch(&[(sha256_hex.clone(), bytes.clone())]) + .expect("put repo bytes"); + + let file = PackFile::from_lazy_repo_bytes( + "rsync://example.test/repo/a.roa", + sha256_hex, + compute_sha256_32(&bytes), + repo_bytes_store, + ); + + assert_eq!(file.bytes().expect("lazy repo bytes"), bytes.as_slice()); + assert_eq!(file.bytes_cloned().expect("cloned repo bytes"), bytes); + assert_eq!(file.compute_sha256().expect("compute sha256"), file.sha256); +} + +#[test] +fn read_only_checkpoint_isolated_from_source_work_db() { + let td = tempfile::tempdir().expect("tempdir"); + let source_path = td.path().join("source-work-db"); + let checkpoint_path = td.path().join("checkpoint-work-db"); + let uri = "rsync://example.test/repo/a.roa"; + let bytes = b"checkpoint-object"; + let hash = sha256_hex(bytes); + { + let source = RocksStore::open(&source_path).expect("open source"); + source + .put_blob_bytes_batch(&[(hash.clone(), bytes.to_vec())]) + .expect("put blob"); + source + .put_repository_view_entry(&RepositoryViewEntry { + rsync_uri: uri.to_string(), + current_hash: Some(hash), + repository_source: Some("fixture".to_string()), + object_type: Some("roa".to_string()), + state: RepositoryViewState::Present, + }) + .expect("put view"); + } + + RocksStore::create_read_only_checkpoint(&source_path, &checkpoint_path) + .expect("create checkpoint"); + let checkpoint = RocksStore::open(&checkpoint_path).expect("open checkpoint"); + checkpoint + .delete_repository_view_entry(uri) + .expect("delete checkpoint entry"); + drop(checkpoint); + + let source = RocksStore::open(&source_path).expect("reopen source"); + assert!( + source + .get_repository_view_entry(uri) + .expect("get source entry") + .is_some() + ); +} + +#[test] +fn read_only_external_repo_bytes_rejects_writes() { + let td = tempfile::tempdir().expect("tempdir"); + let path = td.path().join("repo-bytes.db"); + let hash = sha256_hex(b"repo-object"); + { + let writable = ExternalRepoBytesDb::open(&path).expect("open writable repo bytes"); + writable + .put_blob_bytes_batch(&[(hash.clone(), b"repo-object".to_vec())]) + .expect("seed repo bytes"); + } + let read_only = ExternalRepoBytesDb::open_read_only(&path).expect("open read-only repo bytes"); + assert_eq!( + read_only + .get_blob_bytes(&hash) + .expect("read repo bytes") + .expect("blob"), + b"repo-object" + ); + assert!( + read_only + .put_blob_bytes_batch(&[(hash, b"repo-object".to_vec())]) + .is_err() + ); +} + +#[test] +fn read_only_store_accepts_only_idempotent_existing_blob_apply() { + let td = tempfile::tempdir().expect("tempdir"); + let work_db_path = td.path().join("work-db"); + let repo_bytes_path = td.path().join("repo-bytes.db"); + let bytes = b"repo-object".to_vec(); + let hash = sha256_hex(&bytes); + { + let repo_bytes = ExternalRepoBytesDb::open(&repo_bytes_path).expect("open repo bytes"); + repo_bytes + .put_blob_bytes_batch(&[(hash.clone(), bytes.clone())]) + .expect("seed repo bytes"); + } + let store = + RocksStore::open_with_external_repo_bytes_read_only(&work_db_path, &repo_bytes_path) + .expect("open read-only store"); + store + .put_blob_bytes_batch(&[(hash.clone(), bytes)]) + .expect("idempotent apply"); + assert!( + store + .put_blob_bytes_batch(&[(hash, b"different".to_vec())]) + .is_err() + ); +} + +#[test] +fn repository_blob_verification_detects_missing_external_blob() { + let td = tempfile::tempdir().expect("tempdir"); + let repo_bytes_path = td.path().join("repo-bytes.db"); + ExternalRepoBytesDb::open(&repo_bytes_path).expect("create repo bytes"); + let store = RocksStore::open_with_external_repo_bytes_read_only( + &td.path().join("work-db"), + &repo_bytes_path, + ) + .expect("open work db"); + store + .put_repository_view_entry(&RepositoryViewEntry { + rsync_uri: "rsync://example.test/repo/missing.roa".to_string(), + current_hash: Some(sha256_hex(b"missing")), + repository_source: Some("fixture".to_string()), + object_type: Some("roa".to_string()), + state: RepositoryViewState::Present, + }) + .expect("put view"); + let error = store + .verify_current_repository_blobs(16) + .expect_err("missing blob must fail"); + assert!(error.to_string().contains("missing.roa"), "{error}"); +} diff --git a/src/repository/storage/tests_parts/repository.rs b/src/repository/storage/tests_parts/repository.rs new file mode 100644 index 0000000..89a6000 --- /dev/null +++ b/src/repository/storage/tests_parts/repository.rs @@ -0,0 +1,391 @@ +// Storage test group: repository. + +fn sample_rrdp_source_member_record( + notify_uri: &str, + rsync_uri: &str, + serial: u64, +) -> RrdpSourceMemberRecord { + RrdpSourceMemberRecord { + notify_uri: notify_uri.to_string(), + rsync_uri: rsync_uri.to_string(), + current_hash: Some(sha256_hex(rsync_uri.as_bytes())), + object_type: Some("cer".to_string()), + present: true, + last_confirmed_session_id: "session-1".to_string(), + last_confirmed_serial: serial, + last_changed_at: pack_time(serial as i64), + } +} + +#[test] +fn repository_view_and_raw_by_hash_roundtrip() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(td.path()).expect("open rocksdb"); + + let entry1 = sample_repository_view_entry("rsync://example.test/repo/a.cer", b"object-a"); + let entry2 = sample_repository_view_entry("rsync://example.test/repo/sub/b.roa", b"object-b"); + store + .put_repository_view_entry(&entry1) + .expect("put repository view entry1"); + store + .put_repository_view_entry(&entry2) + .expect("put repository view entry2"); + + let got1 = store + .get_repository_view_entry(&entry1.rsync_uri) + .expect("get repository view entry1") + .expect("entry1 exists"); + assert_eq!(got1, entry1); + + let got_prefix = store + .list_repository_view_entries_with_prefix("rsync://example.test/repo/sub/") + .expect("list repository view prefix"); + assert_eq!(got_prefix, vec![entry2.clone()]); + + store + .delete_repository_view_entry(&entry1.rsync_uri) + .expect("delete repository view entry1"); + assert!( + store + .get_repository_view_entry(&entry1.rsync_uri) + .expect("get deleted repository view entry1") + .is_none() + ); + + let raw = sample_raw_by_hash_entry(b"raw-der-object".to_vec()); + store + .put_raw_by_hash_entry(&raw) + .expect("put raw_by_hash entry"); + let got_raw = store + .get_raw_by_hash_entry(&raw.sha256_hex) + .expect("get raw_by_hash entry") + .expect("raw entry exists"); + assert_eq!(got_raw, raw); +} + +#[test] +fn raw_by_hash_routes_to_external_raw_store_when_configured() { + let td = tempfile::tempdir().expect("tempdir"); + let main_db = td.path().join("main-db"); + let raw_db = td.path().join("raw-store.db"); + + let raw = sample_raw_by_hash_entry(b"external-raw".to_vec()); + { + let store = + RocksStore::open_with_external_raw_store(&main_db, &raw_db).expect("open store"); + store.put_raw_by_hash_entry(&raw).expect("put external raw"); + + let got = store + .get_raw_by_hash_entry(&raw.sha256_hex) + .expect("get external raw") + .expect("raw exists"); + assert_eq!(got, raw); + } + + let main_store = RocksStore::open(&main_db).expect("open main only"); + assert!( + main_store + .get_raw_by_hash_entry(&raw.sha256_hex) + .expect("read main store") + .is_none(), + "main db should not contain raw entry when external raw store is configured" + ); +} + +#[test] +fn put_blob_bytes_batch_uses_internal_blob_cf_without_raw_entry() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(td.path()).expect("open rocksdb"); + let bytes = b"internal-blob-only".to_vec(); + let hash = sha256_hex(&bytes); + + store + .put_blob_bytes_batch(&[(hash.clone(), bytes.clone())]) + .expect("put blob bytes"); + + assert_eq!( + store.get_blob_bytes(&hash).expect("get blob bytes"), + Some(bytes.clone()) + ); + assert!( + store + .get_raw_by_hash_entry(&hash) + .expect("get raw entry") + .is_none() + ); +} + +#[test] +fn put_blob_bytes_batch_routes_to_external_raw_store_without_raw_entry() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open_with_external_raw_store( + &td.path().join("main-db"), + &td.path().join("raw-store.db"), + ) + .expect("open store"); + let bytes = b"external-blob-only".to_vec(); + let hash = sha256_hex(&bytes); + + store + .put_blob_bytes_batch(&[(hash.clone(), bytes.clone())]) + .expect("put external blob bytes"); + + assert_eq!(store.get_blob_bytes(&hash).unwrap(), Some(bytes)); + assert!(store.get_raw_by_hash_entry(&hash).unwrap().is_none()); +} + +#[test] +fn repo_bytes_db_is_physically_separate_from_external_raw_store() { + let td = tempfile::tempdir().expect("tempdir"); + let main_db = td.path().join("main-db"); + let raw_db = td.path().join("raw-store.db"); + let repo_bytes_db = td.path().join("repo-bytes.db"); + let store = + RocksStore::open_with_external_stores(&main_db, Some(&raw_db), Some(&repo_bytes_db)) + .expect("open store"); + let repo_bytes = b"repo-object".to_vec(); + let repo_hash = sha256_hex(&repo_bytes); + let raw = sample_raw_by_hash_entry(b"raw-evidence".to_vec()); + + store + .put_blob_bytes_batch(&[(repo_hash.clone(), repo_bytes.clone())]) + .expect("put repo bytes"); + store.put_raw_by_hash_entry(&raw).expect("put raw evidence"); + + assert_eq!(store.get_blob_bytes(&repo_hash).unwrap(), Some(repo_bytes)); + assert_eq!( + store.get_raw_by_hash_entry(&raw.sha256_hex).unwrap(), + Some(raw.clone()) + ); + drop(store); + + let raw_only = RocksStore::open_with_external_raw_store(&td.path().join("raw-reader"), &raw_db) + .expect("open raw only"); + assert!( + raw_only.get_blob_bytes(&repo_hash).unwrap().is_none(), + "repo object bytes must not be written into raw-store.db" + ); + + let repo_only = + RocksStore::open_with_external_repo_bytes(&td.path().join("repo-reader"), &repo_bytes_db) + .expect("open repo bytes only"); + assert_eq!( + repo_only.get_blob_bytes(&repo_hash).unwrap(), + Some(b"repo-object".to_vec()) + ); + assert!( + repo_only.get_blob_bytes(&raw.sha256_hex).unwrap().is_none(), + "raw evidence bytes must not be written into repo-bytes.db" + ); +} + +#[test] +fn memory_snapshot_includes_work_db_and_external_stores() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open_with_external_stores( + &td.path().join("main-db"), + Some(&td.path().join("raw-store.db")), + Some(&td.path().join("repo-bytes.db")), + ) + .expect("open store"); + + let snapshot = store.memory_snapshot(); + let labels: Vec<&str> = snapshot + .databases + .iter() + .map(|db| db.label.as_str()) + .collect(); + assert_eq!(labels, vec!["work-db", "raw-store.db", "repo-bytes.db"]); + assert!( + snapshot.databases[0] + .column_families + .iter() + .any(|cf| cf.name == CF_REPOSITORY_VIEW) + ); + serde_json::to_value(&snapshot).expect("serialize memory snapshot"); +} + +#[test] +fn put_blob_bytes_batch_accepts_empty_batch_with_external_raw_store() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open_with_external_raw_store( + &td.path().join("main-db"), + &td.path().join("raw-store.db"), + ) + .expect("open store"); + + store + .put_blob_bytes_batch(&[]) + .expect("empty external blob batch should be a no-op"); +} + +#[test] +fn get_blob_bytes_internal_falls_back_to_raw_entry_when_blob_missing() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(td.path()).expect("open rocksdb"); + let raw = sample_raw_by_hash_entry(b"raw-fallback".to_vec()); + + store.put_raw_by_hash_entry(&raw).expect("put raw entry"); + + assert_eq!( + store + .get_blob_bytes(&raw.sha256_hex) + .expect("get blob bytes via raw fallback"), + Some(raw.bytes.clone()) + ); +} + +#[test] +fn get_blob_bytes_batch_internal_prefers_blob_cf_and_falls_back_to_raw_entry() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(td.path()).expect("open rocksdb"); + + let blob_bytes = b"blob-cf-object".to_vec(); + let blob_hash = sha256_hex(&blob_bytes); + store + .put_blob_bytes_batch(&[(blob_hash.clone(), blob_bytes.clone())]) + .expect("put blob bytes"); + + let raw = sample_raw_by_hash_entry(b"raw-fallback-batch".to_vec()); + store.put_raw_by_hash_entry(&raw).expect("put raw fallback"); + + let batch = store + .get_blob_bytes_batch(&[blob_hash.clone(), raw.sha256_hex.clone(), "00".repeat(32)]) + .expect("get blob bytes batch"); + assert_eq!(batch, vec![Some(blob_bytes), Some(raw.bytes.clone()), None]); +} + +#[test] +fn get_blob_bytes_batch_routes_to_external_raw_store_without_raw_entry() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open_with_external_raw_store( + &td.path().join("main-db"), + &td.path().join("raw-store.db"), + ) + .expect("open store"); + let bytes = b"external-batch-blob".to_vec(); + let hash = sha256_hex(&bytes); + + store + .put_blob_bytes_batch(&[(hash.clone(), bytes.clone())]) + .expect("put external blob bytes"); + + assert_eq!( + store + .get_blob_bytes_batch(&[hash, "00".repeat(32)]) + .expect("get external blob batch"), + vec![Some(bytes), None] + ); +} + +#[test] +fn get_blob_bytes_rejects_invalid_hash_for_internal_store() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(td.path()).expect("open rocksdb"); + + let err = store + .get_blob_bytes("not-a-valid-hash") + .expect_err("invalid hash must fail"); + assert!(matches!(err, StorageError::InvalidData { .. })); +} + +#[test] +fn get_blob_bytes_batch_rejects_invalid_hash_for_internal_store() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(td.path()).expect("open rocksdb"); + + let err = store + .get_blob_bytes_batch(&["not-a-valid-hash".to_string()]) + .expect_err("invalid hash must fail"); + assert!(matches!(err, StorageError::InvalidData { .. })); +} + +#[test] +fn get_blob_bytes_batch_returns_empty_for_empty_request_internal() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(td.path()).expect("open rocksdb"); + + assert!( + store + .get_blob_bytes_batch(&[]) + .expect("empty blob batch request") + .is_empty() + ); +} + +#[test] +fn put_blob_bytes_batch_accepts_empty_batch() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(td.path()).expect("open rocksdb"); + + store + .put_blob_bytes_batch(&[]) + .expect("empty blob batch should be a no-op"); +} + +#[test] +fn put_blob_bytes_batch_rejects_empty_bytes() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(td.path()).expect("open rocksdb"); + + let err = store + .put_blob_bytes_batch(&[(sha256_hex(b"valid"), Vec::new())]) + .expect_err("empty bytes must fail"); + assert!(matches!(err, StorageError::InvalidData { .. })); +} + +#[test] +fn delete_raw_by_hash_entry_internal_preserves_blob_bytes() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(td.path()).expect("open rocksdb"); + let bytes = b"blob-persists-after-raw-delete".to_vec(); + let hash = sha256_hex(&bytes); + let raw = RawByHashEntry::from_bytes(hash.clone(), bytes.clone()); + + store + .put_blob_bytes_batch(&[(hash.clone(), bytes.clone())]) + .expect("put blob bytes"); + store.put_raw_by_hash_entry(&raw).expect("put raw entry"); + + store + .delete_raw_by_hash_entry(&hash) + .expect("delete raw entry only"); + + assert!(store.get_raw_by_hash_entry(&hash).unwrap().is_none()); + assert_eq!(store.get_blob_bytes(&hash).unwrap(), Some(bytes)); +} + +#[test] +fn delete_raw_by_hash_entry_rejects_invalid_hash() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(td.path()).expect("open rocksdb"); + + let err = store + .delete_raw_by_hash_entry("not-a-valid-hash") + .expect_err("invalid hash must fail"); + assert!(matches!(err, StorageError::InvalidData { .. })); +} + +#[test] +fn delete_raw_by_hash_entry_routes_to_external_raw_store() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open_with_external_raw_store( + &td.path().join("main-db"), + &td.path().join("raw-store.db"), + ) + .expect("open store"); + let raw = sample_raw_by_hash_entry(b"external-delete".to_vec()); + + store.put_raw_by_hash_entry(&raw).expect("put raw entry"); + store + .delete_raw_by_hash_entry(&raw.sha256_hex) + .expect("delete external raw entry"); + + assert!( + store + .get_raw_by_hash_entry(&raw.sha256_hex) + .unwrap() + .is_none() + ); + assert!(store.get_blob_bytes(&raw.sha256_hex).unwrap().is_none()); +} diff --git a/src/repository/storage/tests_parts/rrdp.rs b/src/repository/storage/tests_parts/rrdp.rs new file mode 100644 index 0000000..879d63c --- /dev/null +++ b/src/repository/storage/tests_parts/rrdp.rs @@ -0,0 +1,262 @@ +// Storage test group: rrdp. + +#[test] +fn projection_batch_roundtrip_writes_repository_view_member_and_owner_records() { + let dir = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(dir.path()).expect("open store"); + + let view = RepositoryViewEntry { + rsync_uri: "rsync://example.test/repo/a.roa".to_string(), + current_hash: Some(hex::encode([1u8; 32])), + repository_source: Some("https://example.test/notify.xml".to_string()), + object_type: Some("roa".to_string()), + state: RepositoryViewState::Present, + }; + let member = RrdpSourceMemberRecord { + notify_uri: "https://example.test/notify.xml".to_string(), + rsync_uri: "rsync://example.test/repo/a.roa".to_string(), + current_hash: Some(hex::encode([1u8; 32])), + object_type: Some("roa".to_string()), + present: true, + last_confirmed_session_id: "session-1".to_string(), + last_confirmed_serial: 7, + last_changed_at: pack_time(1), + }; + let owner = RrdpUriOwnerRecord { + rsync_uri: "rsync://example.test/repo/a.roa".to_string(), + notify_uri: "https://example.test/notify.xml".to_string(), + current_hash: Some(hex::encode([1u8; 32])), + last_confirmed_session_id: "session-1".to_string(), + last_confirmed_serial: 7, + last_changed_at: pack_time(1), + owner_state: RrdpUriOwnerState::Active, + }; + + store + .put_projection_batch( + std::slice::from_ref(&view), + std::slice::from_ref(&member), + std::slice::from_ref(&owner), + ) + .expect("write projection batch"); + + assert_eq!( + store + .get_repository_view_entry(&view.rsync_uri) + .expect("get view") + .expect("present view"), + view + ); + assert_eq!( + store + .get_rrdp_source_member_record(&member.notify_uri, &member.rsync_uri) + .expect("get member") + .expect("present member"), + member + ); + assert_eq!( + store + .get_rrdp_uri_owner_record(&owner.rsync_uri) + .expect("get owner") + .expect("present owner"), + owner + ); +} + +#[test] +fn current_rrdp_source_member_helpers_filter_present_records() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(td.path()).expect("open rocksdb"); + + let notify_uri = "https://rrdp.example.test/notification.xml"; + let mut present_a = + sample_rrdp_source_member_record(notify_uri, "rsync://example.test/repo/a.cer", 1); + let mut withdrawn_b = + sample_rrdp_source_member_record(notify_uri, "rsync://example.test/repo/b.roa", 2); + withdrawn_b.present = false; + let present_c = + sample_rrdp_source_member_record(notify_uri, "rsync://example.test/repo/c.crl", 3); + let other_source = sample_rrdp_source_member_record( + "https://other.example.test/notification.xml", + "rsync://other.example.test/repo/x.cer", + 4, + ); + present_a.last_confirmed_serial = 10; + + store + .put_rrdp_source_member_record(&present_a) + .expect("put present a"); + store + .put_rrdp_source_member_record(&withdrawn_b) + .expect("put withdrawn b"); + store + .put_rrdp_source_member_record(&present_c) + .expect("put present c"); + store + .put_rrdp_source_member_record(&other_source) + .expect("put other source"); + + let members = store + .list_current_rrdp_source_members(notify_uri) + .expect("list current members"); + assert_eq!( + members + .iter() + .map(|record| record.rsync_uri.as_str()) + .collect::>(), + vec![ + "rsync://example.test/repo/a.cer", + "rsync://example.test/repo/c.crl", + ] + ); + + assert!( + store + .is_current_rrdp_source_member(notify_uri, &present_a.rsync_uri) + .expect("current a") + ); + assert!( + !store + .is_current_rrdp_source_member(notify_uri, &withdrawn_b.rsync_uri) + .expect("withdrawn b") + ); + assert!( + !store + .is_current_rrdp_source_member(notify_uri, &other_source.rsync_uri) + .expect("other source") + ); +} + +#[test] +fn load_current_object_bytes_by_uri_uses_repository_view_and_raw_by_hash() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(td.path()).expect("open rocksdb"); + + let present_bytes = b"present-object".to_vec(); + let present_hash = sha256_hex(&present_bytes); + let mut present_raw = RawByHashEntry::from_bytes(present_hash.clone(), present_bytes.clone()); + present_raw + .origin_uris + .push("rsync://example.test/repo/present.roa".to_string()); + present_raw.object_type = Some("roa".to_string()); + store + .put_raw_by_hash_entry(&present_raw) + .expect("put present raw"); + store + .put_repository_view_entry(&RepositoryViewEntry { + rsync_uri: "rsync://example.test/repo/present.roa".to_string(), + current_hash: Some(present_hash), + repository_source: Some("https://rrdp.example.test/notification.xml".to_string()), + object_type: Some("roa".to_string()), + state: RepositoryViewState::Present, + }) + .expect("put present view"); + + let replaced_bytes = b"replaced-object".to_vec(); + let replaced_hash = sha256_hex(&replaced_bytes); + let mut replaced_raw = + RawByHashEntry::from_bytes(replaced_hash.clone(), replaced_bytes.clone()); + replaced_raw + .origin_uris + .push("rsync://example.test/repo/replaced.cer".to_string()); + replaced_raw.object_type = Some("cer".to_string()); + store + .put_raw_by_hash_entry(&replaced_raw) + .expect("put replaced raw"); + store + .put_repository_view_entry(&RepositoryViewEntry { + rsync_uri: "rsync://example.test/repo/replaced.cer".to_string(), + current_hash: Some(replaced_hash), + repository_source: Some("https://rrdp.example.test/notification.xml".to_string()), + object_type: Some("cer".to_string()), + state: RepositoryViewState::Replaced, + }) + .expect("put replaced view"); + + store + .put_repository_view_entry(&RepositoryViewEntry { + rsync_uri: "rsync://example.test/repo/withdrawn.crl".to_string(), + current_hash: Some(sha256_hex(b"withdrawn")), + repository_source: Some("https://rrdp.example.test/notification.xml".to_string()), + object_type: Some("crl".to_string()), + state: RepositoryViewState::Withdrawn, + }) + .expect("put withdrawn view"); + + assert_eq!( + store + .load_current_object_bytes_by_uri("rsync://example.test/repo/present.roa") + .expect("load present"), + Some(present_bytes) + ); + assert_eq!( + store + .load_current_object_bytes_by_uri("rsync://example.test/repo/replaced.cer") + .expect("load replaced"), + Some(replaced_bytes) + ); + assert_eq!( + store + .load_current_object_bytes_by_uri("rsync://example.test/repo/withdrawn.crl") + .expect("load withdrawn"), + None + ); + assert_eq!( + store + .load_current_object_bytes_by_uri("rsync://example.test/repo/missing.roa") + .expect("load missing"), + None + ); +} + +#[test] +fn load_current_object_bytes_by_uri_errors_when_raw_by_hash_is_missing() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(td.path()).expect("open rocksdb"); + let rsync_uri = "rsync://example.test/repo/missing.cer"; + + store + .put_repository_view_entry(&RepositoryViewEntry { + rsync_uri: rsync_uri.to_string(), + current_hash: Some(hex::encode([0x11; 32])), + repository_source: Some("https://rrdp.example.test/notification.xml".to_string()), + object_type: Some("cer".to_string()), + state: RepositoryViewState::Present, + }) + .expect("put view"); + let err = store + .load_current_object_bytes_by_uri(rsync_uri) + .expect_err("missing raw_by_hash should error"); + assert!(matches!(err, StorageError::InvalidData { .. })); +} + +#[test] +fn load_current_object_with_hash_by_uri_returns_hash_and_bytes() { + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(td.path()).expect("open rocksdb"); + let rsync_uri = "rsync://example.test/repo/present.roa"; + let bytes = b"present-object".to_vec(); + let hash = sha256_hex(&bytes); + + let mut raw = RawByHashEntry::from_bytes(hash.clone(), bytes.clone()); + raw.origin_uris.push(rsync_uri.to_string()); + raw.object_type = Some("roa".to_string()); + store.put_raw_by_hash_entry(&raw).expect("put raw"); + store + .put_repository_view_entry(&RepositoryViewEntry { + rsync_uri: rsync_uri.to_string(), + current_hash: Some(hash.clone()), + repository_source: Some("https://rrdp.example.test/notification.xml".to_string()), + object_type: Some("roa".to_string()), + state: RepositoryViewState::Present, + }) + .expect("put view"); + + let got = store + .load_current_object_with_hash_by_uri(rsync_uri) + .expect("load current object") + .expect("current object exists"); + assert_eq!(got.current_hash_hex, hash); + assert_eq!(got.current_hash, compute_sha256_32(&bytes)); + assert_eq!(got.bytes, bytes); +} diff --git a/src/repository/storage/verification.rs b/src/repository/storage/verification.rs new file mode 100644 index 0000000..c897d45 --- /dev/null +++ b/src/repository/storage/verification.rs @@ -0,0 +1,32 @@ +// Repository blob integrity verification helper. + +fn verify_repository_blob_batch( + store: &RocksStore, + batch: &[(String, String)], + summary: &mut RepositoryBlobVerificationSummary, +) -> StorageResult<()> { + let hashes = batch + .iter() + .map(|(_, hash)| hash.clone()) + .collect::>(); + let blobs = store.get_blob_bytes_batch(&hashes)?; + for ((uri, expected_hash), blob) in batch.iter().zip(blobs.into_iter()) { + let bytes = blob.as_ref().ok_or(StorageError::InvalidData { + entity: "repository_blob_verification", + detail: format!("blob missing for URI {uri} (hash={expected_hash})"), + })?; + let actual_hash = hex::encode(compute_sha256_32(bytes)); + if !actual_hash.eq_ignore_ascii_case(expected_hash) { + return Err(StorageError::InvalidData { + entity: "repository_blob_verification", + detail: format!( + "blob hash mismatch for URI {uri}: expected={expected_hash}, actual={actual_hash}" + ), + }); + } + summary.current_objects += 1; + summary.bytes_verified += bytes.len() as u64; + } + summary.batches += 1; + Ok(()) +} diff --git a/src/repository/sync/mod.rs b/src/repository/sync/mod.rs new file mode 100644 index 0000000..7d88f66 --- /dev/null +++ b/src/repository/sync/mod.rs @@ -0,0 +1,3 @@ +pub mod repo; +pub mod rrdp; +pub(crate) mod store_projection; diff --git a/src/repository/sync/repo.rs b/src/repository/sync/repo.rs new file mode 100644 index 0000000..71460af --- /dev/null +++ b/src/repository/sync/repo.rs @@ -0,0 +1,516 @@ +#![allow(clippy::too_many_arguments)] + +use crate::output::analysis::timing::TimingHandle; +use crate::output::audit::AuditDownloadKind; +use crate::output::audit_downloads::DownloadLogHandle; +use crate::output::report::{RfcRef, Warning}; +use crate::repository::current_repo_index::CurrentRepoIndexHandle; +use crate::repository::fetch::rsync::{RsyncFetchError, RsyncFetcher}; +use crate::repository::storage::RocksStore; +use crate::repository::sync::rrdp::sync_from_notification_with_timing_and_download_log; +use crate::repository::sync::rrdp::{ + Fetcher as HttpFetcher, RrdpFetchError, RrdpOrigin, RrdpResourceKind, RrdpSyncError, +}; +use crate::repository::sync::store_projection::{ + build_repository_view_present_entry, build_repository_view_withdrawn_entry, + prepare_repo_bytes_batch_owned, +}; +use crate::validation::policy::{Policy, SyncPreference}; +use std::collections::HashSet; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RepoSyncSource { + Rrdp, + Rsync, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RepoSyncPhase { + RrdpOk, + RrdpFailedRsyncOk, + RsyncOnlyOk, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RepoSyncResult { + pub source: RepoSyncSource, + pub phase: RepoSyncPhase, + pub objects_written: usize, + pub warnings: Vec, +} + +#[derive(Debug, thiserror::Error)] +pub enum RepoSyncError { + #[error("RRDP sync failed: {0}")] + Rrdp(#[from] RrdpSyncError), + + #[error("rsync fallback failed: {0}")] + Rsync(#[from] RsyncFetchError), + + #[error("storage error: {0}")] + Storage(String), +} + +/// Sync a publication point into the current repository view. +/// +/// v1 behavior: +/// - If `rrdp_notification_uri` is present and `policy.sync_preference` is `rrdp_then_rsync`, +/// try RRDP snapshot sync first (RFC 8182 §3.4.1-§3.4.3). +/// - On RRDP failure, fall back to rsync (RFC 8182 §3.4.5). +/// - If `sync_preference` is `rsync_only` or there is no RRDP URI, use rsync. +pub fn sync_publication_point( + store: &RocksStore, + policy: &Policy, + rrdp_notification_uri: Option<&str>, + rsync_base_uri: &str, + http_fetcher: &dyn HttpFetcher, + rsync_fetcher: &dyn RsyncFetcher, + timing: Option<&TimingHandle>, + download_log: Option<&DownloadLogHandle>, +) -> Result { + match (policy.sync_preference, rrdp_notification_uri) { + (SyncPreference::RrdpThenRsync, Some(notification_uri)) => { + match try_rrdp_sync_with_retry( + store, + notification_uri, + None, + http_fetcher, + timing, + download_log, + ) { + Ok(written) => { + if let Some(t) = timing.as_ref() { + t.record_count("repo_sync_rrdp_ok_total", 1); + t.record_count("repo_sync_rrdp_objects_written_total", written as u64); + } + crate::logging::progress::emit!( + "repo_sync_rrdp_ok", + serde_json::json!({ + "notify_uri": notification_uri, + "objects_written": written, + }), + ); + Ok(RepoSyncResult { + source: RepoSyncSource::Rrdp, + phase: RepoSyncPhase::RrdpOk, + objects_written: written, + warnings: Vec::new(), + }) + } + Err(err) => { + if let Some(t) = timing.as_ref() { + t.record_count("repo_sync_rrdp_failed_total", 1); + } + crate::logging::progress::emit!( + "rrdp_fallback_rsync", + serde_json::json!({ + "notify_uri": notification_uri, + "rsync_base_uri": rsync_base_uri, + "rrdp_error": err.to_string(), + }), + ); + crate::logging::progress::emit!( + "rrdp_failed_fallback_rsync", + serde_json::json!({ + "notify_uri": notification_uri, + "rsync_base_uri": rsync_base_uri, + "rrdp_error": err.to_string(), + }), + ); + let mut rfc_refs = vec![RfcRef("RFC 8182 §3.4.5")]; + if matches!(&err, RrdpSyncError::Rrdp(e) if e.is_cross_origin_violation()) { + rfc_refs.push(RfcRef("RFC 9674 §3.2")); + } + let warnings = vec![ + Warning::new(format!("RRDP failed; falling back to rsync: {err}")) + .with_rfc_refs(&rfc_refs) + .with_context(notification_uri), + ]; + let written = rsync_sync_into_current_store( + store, + rsync_base_uri, + None, + rsync_fetcher, + timing, + download_log, + )?; + if let Some(t) = timing.as_ref() { + t.record_count("repo_sync_rsync_peer_aligned_profile_total", 1); + } + if let Some(t) = timing.as_ref() { + t.record_count("repo_sync_rsync_fallback_ok_total", 1); + t.record_count("repo_sync_rsync_objects_written_total", written as u64); + } + Ok(RepoSyncResult { + source: RepoSyncSource::Rsync, + phase: RepoSyncPhase::RrdpFailedRsyncOk, + objects_written: written, + warnings, + }) + } + } + } + _ => { + let written = rsync_sync_into_current_store( + store, + rsync_base_uri, + None, + rsync_fetcher, + timing, + download_log, + )?; + if let Some(t) = timing.as_ref() { + t.record_count("repo_sync_rsync_peer_aligned_profile_total", 1); + } + crate::logging::progress::emit!( + "repo_sync_rsync_direct", + serde_json::json!({ + "rsync_base_uri": rsync_base_uri, + "objects_written": written, + }), + ); + if let Some(t) = timing.as_ref() { + t.record_count("repo_sync_rsync_direct_total", 1); + t.record_count("repo_sync_rsync_objects_written_total", written as u64); + } + Ok(RepoSyncResult { + source: RepoSyncSource::Rsync, + phase: RepoSyncPhase::RsyncOnlyOk, + objects_written: written, + warnings: Vec::new(), + }) + } + } +} + +fn try_rrdp_sync( + store: &RocksStore, + notification_uri: &str, + current_repo_index: Option<&CurrentRepoIndexHandle>, + http_fetcher: &dyn HttpFetcher, + timing: Option<&TimingHandle>, + download_log: Option<&DownloadLogHandle>, +) -> Result { + let notification_origin = RrdpOrigin::parse(notification_uri, RrdpResourceKind::Notification)?; + let notification_xml = { + let _step = timing + .as_ref() + .map(|t| t.span_rrdp_repo_step(notification_uri, "fetch_notification")); + let _total = timing + .as_ref() + .map(|t| t.span_phase("rrdp_fetch_notification_total")); + let mut dl_span = download_log + .map(|dl| dl.span_download(AuditDownloadKind::RrdpNotification, notification_uri)); + match http_fetcher.fetch_rrdp( + RrdpResourceKind::Notification, + notification_uri, + ¬ification_origin, + ) { + Ok(v) => { + if let Some(t) = timing.as_ref() { + t.record_count("rrdp_notification_fetch_ok_total", 1); + } + if let Some(s) = dl_span.as_mut() { + s.set_bytes(v.len() as u64); + s.set_ok(); + } + v + } + Err(RrdpFetchError::Fetch(e)) => { + if let Some(t) = timing.as_ref() { + t.record_count("rrdp_notification_fetch_fail_total", 1); + } + if let Some(s) = dl_span.as_mut() { + s.set_err(e.clone()); + } + return Err(RrdpSyncError::Fetch(e)); + } + Err(RrdpFetchError::Rrdp(e)) => { + if let Some(t) = timing.as_ref() { + t.record_count("rrdp_notification_fetch_fail_total", 1); + if e.is_cross_origin_violation() { + t.record_count("rrdp_rejected_cross_origin_total", 1); + } + } + if let Some(s) = dl_span.as_mut() { + s.set_err(e.to_string()); + } + return Err(e.into()); + } + } + }; + if let Some(t) = timing.as_ref() { + t.record_count( + "rrdp_notification_bytes_total", + notification_xml.len() as u64, + ); + } + + sync_from_notification_with_timing_and_download_log( + store, + notification_uri, + current_repo_index, + ¬ification_xml, + http_fetcher, + timing, + download_log, + ) +} + +fn is_retryable_http_fetch_error(msg: &str) -> bool { + if msg.contains("http request failed:") || msg.contains("http read body failed:") { + return true; + } + let Some(rest) = msg.strip_prefix("http status ") else { + return false; + }; + let code = rest + .split_whitespace() + .next() + .and_then(|s| s.parse::().ok()) + .unwrap_or(0); + code == 408 || code == 429 || (500..600).contains(&code) +} + +fn try_rrdp_sync_with_retry( + store: &RocksStore, + notification_uri: &str, + current_repo_index: Option<&CurrentRepoIndexHandle>, + http_fetcher: &dyn HttpFetcher, + timing: Option<&TimingHandle>, + download_log: Option<&DownloadLogHandle>, +) -> Result { + let attempt = 1usize; + crate::logging::progress::emit!( + "rrdp_sync_attempt", + serde_json::json!({ + "notify_uri": notification_uri, + "attempt": attempt, + }), + ); + if let Some(t) = timing.as_ref() { + t.record_count("rrdp_retry_attempt_total", 1); + } + + match try_rrdp_sync( + store, + notification_uri, + current_repo_index, + http_fetcher, + timing, + download_log, + ) { + Ok(written) => { + crate::logging::progress::emit!( + "rrdp_sync_success", + serde_json::json!({ + "notify_uri": notification_uri, + "attempt": attempt, + "objects_written": written, + }), + ); + Ok(written) + } + Err(err) => { + let retryable = match &err { + RrdpSyncError::Fetch(msg) => is_retryable_http_fetch_error(msg), + _ => false, + }; + crate::logging::progress::emit!( + "rrdp_sync_failed", + serde_json::json!({ + "notify_uri": notification_uri, + "attempt": attempt, + "retryable": retryable, + "error": err.to_string(), + }), + ); + if let Some(t) = timing.as_ref() { + match &err { + RrdpSyncError::Fetch(_) => t.record_count("rrdp_failed_fetch_total", 1), + RrdpSyncError::Rrdp(_) => t.record_count("rrdp_failed_protocol_total", 1), + RrdpSyncError::Storage(_) => t.record_count("rrdp_failed_storage_total", 1), + } + } + Err(err) + } + } +} + +pub(crate) fn run_rrdp_transport( + store: &RocksStore, + notification_uri: &str, + current_repo_index: Option<&CurrentRepoIndexHandle>, + http_fetcher: &dyn HttpFetcher, + timing: Option<&TimingHandle>, + download_log: Option<&DownloadLogHandle>, +) -> Result { + try_rrdp_sync_with_retry( + store, + notification_uri, + current_repo_index, + http_fetcher, + timing, + download_log, + ) +} + +fn rsync_sync_into_current_store( + store: &RocksStore, + rsync_base_uri: &str, + current_repo_index: Option<&CurrentRepoIndexHandle>, + rsync_fetcher: &dyn RsyncFetcher, + timing: Option<&TimingHandle>, + download_log: Option<&DownloadLogHandle>, +) -> Result { + let started = std::time::Instant::now(); + let sync_scope_uri = rsync_fetcher.dedup_key(rsync_base_uri); + crate::logging::progress::emit!( + "rsync_sync_start", + serde_json::json!({ + "rsync_base_uri": rsync_base_uri, + "sync_scope_uri": &sync_scope_uri, + }), + ); + let _s = timing + .as_ref() + .map(|t| t.span_rrdp_repo_step(rsync_base_uri, "rsync_fetch_objects")); + let _p = timing.as_ref().map(|t| t.span_phase("rsync_fetch_total")); + let mut dl_span = + download_log.map(|dl| dl.span_download(AuditDownloadKind::Rsync, rsync_base_uri)); + let mut new_set: HashSet = HashSet::new(); + let mut fetched_objects: Vec<(String, Vec)> = Vec::new(); + let (object_count, bytes_total) = + match rsync_fetcher.visit_objects(rsync_base_uri, &mut |uri, bytes| { + new_set.insert(uri.clone()); + fetched_objects.push((uri, bytes)); + Ok(()) + }) { + Ok(v) => { + if let Some(s) = dl_span.as_mut() { + s.set_objects(v.0 as u64, v.1); + s.set_bytes(v.1); + s.set_ok(); + } + v + } + Err(e) => { + if let Some(s) = dl_span.as_mut() { + s.set_err(e.to_string()); + } + return Err(e.into()); + } + }; + crate::logging::progress::emit!( + "rsync_sync_fetch_done", + serde_json::json!({ + "rsync_base_uri": rsync_base_uri, + "sync_scope_uri": &sync_scope_uri, + "object_count": object_count, + "bytes_total": bytes_total, + "duration_ms": started.elapsed().as_millis() as u64, + }), + ); + if let Some(t) = timing.as_ref() { + t.record_count("rsync_objects_fetched_total", object_count as u64); + t.record_count("rsync_objects_bytes_total", bytes_total); + } + drop(_p); + + let existing_view = store + .list_repository_view_entries_with_prefix(&sync_scope_uri) + .map_err(|e| RepoSyncError::Storage(e.to_string()))?; + + let _proj = timing + .as_ref() + .map(|t| t.span_phase("rsync_write_current_store_total")); + let prepared_bytes = + prepare_repo_bytes_batch_owned(fetched_objects).map_err(RepoSyncError::Storage)?; + let mut repository_view_entries = Vec::new(); + for entry in existing_view { + if !new_set.contains(&entry.rsync_uri) { + repository_view_entries.push(build_repository_view_withdrawn_entry( + &sync_scope_uri, + &entry.rsync_uri, + entry.current_hash, + )); + } + } + + for uri in &new_set { + let current_hash = prepared_bytes + .uri_to_hash + .get(uri) + .cloned() + .ok_or_else(|| { + RepoSyncError::Storage(format!("missing raw_by_hash mapping for {uri}")) + })?; + repository_view_entries.push(build_repository_view_present_entry( + &sync_scope_uri, + uri, + ¤t_hash, + )); + } + + store + .put_blob_bytes_batch(&prepared_bytes.blobs_to_write) + .map_err(|e| RepoSyncError::Storage(e.to_string()))?; + store + .put_projection_batch(&repository_view_entries, &[], &[]) + .map_err(|e| RepoSyncError::Storage(e.to_string()))?; + if let Some(index) = current_repo_index { + index + .write() + .map_err(|_| RepoSyncError::Storage("current repo index lock poisoned".to_string()))? + .apply_repository_view_entries(&repository_view_entries) + .map_err(RepoSyncError::Storage)?; + } + + let total_duration_ms = started.elapsed().as_millis() as u64; + crate::logging::progress::emit!( + "rsync_sync_done", + serde_json::json!({ + "rsync_base_uri": rsync_base_uri, + "sync_scope_uri": &sync_scope_uri, + "object_count": object_count, + "bytes_total": bytes_total, + "duration_ms": total_duration_ms, + }), + ); + if (total_duration_ms as f64) / 1000.0 >= crate::logging::progress::slow_threshold_secs() { + crate::logging::progress::emit!( + "rsync_sync_slow", + serde_json::json!({ + "rsync_base_uri": rsync_base_uri, + "sync_scope_uri": &sync_scope_uri, + "object_count": object_count, + "bytes_total": bytes_total, + "duration_ms": total_duration_ms, + }), + ); + } + + Ok(object_count) +} + +pub(crate) fn run_rsync_transport( + store: &RocksStore, + rsync_base_uri: &str, + current_repo_index: Option<&CurrentRepoIndexHandle>, + rsync_fetcher: &dyn RsyncFetcher, + timing: Option<&TimingHandle>, + download_log: Option<&DownloadLogHandle>, +) -> Result { + rsync_sync_into_current_store( + store, + rsync_base_uri, + current_repo_index, + rsync_fetcher, + timing, + download_log, + ) +} + +#[cfg(test)] +#[path = "repo/tests.rs"] +mod tests; diff --git a/src/repository/sync/repo/tests.rs b/src/repository/sync/repo/tests.rs new file mode 100644 index 0000000..e5693f3 --- /dev/null +++ b/src/repository/sync/repo/tests.rs @@ -0,0 +1,2 @@ +// Repository sync tests cover public RRDP/rsync transport behavior. +include!("tests_parts/setup_and_sync.rs"); diff --git a/src/repository/sync/repo/tests_parts/setup_and_sync.rs b/src/repository/sync/repo/tests_parts/setup_and_sync.rs new file mode 100644 index 0000000..191006a --- /dev/null +++ b/src/repository/sync/repo/tests_parts/setup_and_sync.rs @@ -0,0 +1,371 @@ +// Repository sync test group: setup and sync. + +use super::*; +use crate::output::analysis::timing::{TimingHandle, TimingMeta}; +use crate::repository::fetch::rsync::LocalDirRsyncFetcher; +use crate::repository::storage::RepositoryViewState; +use crate::repository::sync::rrdp::Fetcher as HttpFetcher; +use crate::repository::sync::store_projection::{build_repository_view_present_entry, compute_sha256_hex}; +use sha2::Digest; +use std::sync::atomic::{AtomicUsize, Ordering}; + +struct DummyHttpFetcher; + +impl HttpFetcher for DummyHttpFetcher { + fn fetch(&self, _url: &str) -> Result, String> { + panic!("http fetcher must not be used in rsync-only mode") + } +} + +fn assert_current_object(store: &RocksStore, uri: &str, expected: &[u8]) { + assert_eq!( + store + .load_current_object_bytes_by_uri(uri) + .expect("load current object"), + Some(expected.to_vec()) + ); +} + +#[test] +fn rsync_sync_uses_fetcher_dedup_scope_for_repository_view_projection() { + struct ScopeFetcher; + impl RsyncFetcher for ScopeFetcher { + fn fetch_objects( + &self, + _rsync_base_uri: &str, + ) -> Result)>, RsyncFetchError> { + Ok(vec![( + "rsync://example.net/repo/child/a.mft".to_string(), + b"manifest".to_vec(), + )]) + } + + fn dedup_key(&self, _rsync_base_uri: &str) -> String { + "rsync://example.net/repo/".to_string() + } + } + + let td = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(td.path()).expect("open rocksdb"); + let seeded = build_repository_view_present_entry( + "rsync://example.net/repo/", + "rsync://example.net/repo/sibling/old.roa", + &compute_sha256_hex(b"old"), + ); + store + .put_projection_batch(&[seeded], &[], &[]) + .expect("seed repository view"); + + let fetcher = ScopeFetcher; + let written = rsync_sync_into_current_store( + &store, + "rsync://example.net/repo/child/", + None, + &fetcher, + None, + None, + ) + .expect("sync ok"); + assert_eq!(written, 1); + + let entries = store + .list_repository_view_entries_with_prefix("rsync://example.net/repo/") + .expect("list repository view"); + let sibling = entries + .iter() + .find(|entry| entry.rsync_uri == "rsync://example.net/repo/sibling/old.roa") + .expect("sibling entry exists"); + assert_eq!(sibling.state, RepositoryViewState::Withdrawn); + let child = entries + .iter() + .find(|entry| entry.rsync_uri == "rsync://example.net/repo/child/a.mft") + .expect("child entry exists"); + assert_eq!(child.state, RepositoryViewState::Present); +} + +fn timing_to_json(temp_dir: &std::path::Path, timing: &TimingHandle) -> serde_json::Value { + let timing_path = temp_dir.join("timing_retry.json"); + timing.write_json(&timing_path, 50).expect("write json"); + serde_json::from_slice(&std::fs::read(&timing_path).expect("read json")).expect("parse json") +} + +#[test] +fn rsync_sync_writes_current_store_and_records_counts() { + let temp = tempfile::tempdir().expect("tempdir"); + + let repo_dir = temp.path().join("repo"); + std::fs::create_dir_all(repo_dir.join("sub")).expect("mkdir"); + std::fs::write(repo_dir.join("a.mft"), b"mft").expect("write"); + std::fs::write(repo_dir.join("sub").join("b.roa"), b"roa").expect("write"); + std::fs::write(repo_dir.join("sub").join("c.cer"), b"cer").expect("write"); + + let store_dir = temp.path().join("db"); + let store = RocksStore::open(&store_dir).expect("open rocksdb"); + + let timing = TimingHandle::new(TimingMeta { + recorded_at_utc_rfc3339: "2026-02-28T00:00:00Z".to_string(), + validation_time_utc_rfc3339: "2026-02-28T00:00:00Z".to_string(), + tal_url: None, + db_path: Some(store_dir.to_string_lossy().into_owned()), + }); + + let policy = Policy { + sync_preference: SyncPreference::RsyncOnly, + ..Policy::default() + }; + let http = DummyHttpFetcher; + let rsync = LocalDirRsyncFetcher::new(&repo_dir); + + let download_log = DownloadLogHandle::new(); + let out = sync_publication_point( + &store, + &policy, + None, + "rsync://example.test/repo/", + &http, + &rsync, + Some(&timing), + Some(&download_log), + ) + .expect("sync ok"); + + assert_eq!(out.source, RepoSyncSource::Rsync); + assert_eq!(out.objects_written, 3); + + let events = download_log.snapshot_events(); + assert_eq!(events.len(), 1); + assert_eq!(events[0].kind, AuditDownloadKind::Rsync); + assert!(events[0].success); + assert_eq!(events[0].bytes, Some(9)); + let objects = events[0].objects.as_ref().expect("objects stat"); + assert_eq!(objects.objects_count, 3); + assert_eq!(objects.objects_bytes_total, 9); + + assert_current_object(&store, "rsync://example.test/repo/a.mft", b"mft"); + assert_current_object(&store, "rsync://example.test/repo/sub/b.roa", b"roa"); + assert_current_object(&store, "rsync://example.test/repo/sub/c.cer", b"cer"); + + let view = store + .get_repository_view_entry("rsync://example.test/repo/a.mft") + .expect("get repository view") + .expect("repository view entry present"); + assert_eq!( + view.current_hash.as_deref(), + Some(hex::encode(sha2::Sha256::digest(b"mft")).as_str()) + ); + assert_eq!( + view.repository_source.as_deref(), + Some("rsync://example.test/repo/") + ); + + let current_bytes = store + .load_current_object_bytes_by_uri("rsync://example.test/repo/sub/b.roa") + .expect("load current bytes") + .expect("current object bytes exist"); + assert_eq!(current_bytes, b"roa".to_vec()); + assert!( + store + .get_raw_by_hash_entry(hex::encode(sha2::Sha256::digest(b"roa")).as_str()) + .expect("get raw_by_hash") + .is_none() + ); + + let timing_path = temp.path().join("timing.json"); + timing.write_json(&timing_path, 5).expect("write json"); + let v: serde_json::Value = + serde_json::from_slice(&std::fs::read(&timing_path).expect("read json")) + .expect("parse json"); + let counts = v.get("counts").expect("counts"); + assert_eq!( + counts + .get("rsync_objects_fetched_total") + .and_then(|v| v.as_u64()), + Some(3) + ); + assert_eq!( + counts + .get("rsync_objects_bytes_total") + .and_then(|v| v.as_u64()), + Some(3 * 3) + ); +} + +#[test] +fn rsync_second_sync_marks_missing_repository_view_entries_withdrawn() { + let temp = tempfile::tempdir().expect("tempdir"); + + let repo_dir = temp.path().join("repo"); + std::fs::create_dir_all(repo_dir.join("sub")).expect("mkdir"); + std::fs::write(repo_dir.join("a.mft"), b"mft-v1").expect("write a"); + std::fs::write(repo_dir.join("sub").join("b.roa"), b"roa-v1").expect("write b"); + + let store_dir = temp.path().join("db"); + let store = RocksStore::open(&store_dir).expect("open rocksdb"); + let policy = Policy { + sync_preference: SyncPreference::RsyncOnly, + ..Policy::default() + }; + let http = DummyHttpFetcher; + let rsync = LocalDirRsyncFetcher::new(&repo_dir); + + sync_publication_point( + &store, + &policy, + None, + "rsync://example.test/repo/", + &http, + &rsync, + None, + None, + ) + .expect("first sync ok"); + + std::fs::remove_file(repo_dir.join("sub").join("b.roa")).expect("remove b"); + std::fs::write(repo_dir.join("c.crl"), b"crl-v2").expect("write c"); + + sync_publication_point( + &store, + &policy, + None, + "rsync://example.test/repo/", + &http, + &rsync, + None, + None, + ) + .expect("second sync ok"); + + let withdrawn = store + .get_repository_view_entry("rsync://example.test/repo/sub/b.roa") + .expect("get withdrawn repo view") + .expect("withdrawn entry exists"); + assert_eq!( + withdrawn.state, + crate::repository::storage::RepositoryViewState::Withdrawn + ); + assert_eq!( + withdrawn.repository_source.as_deref(), + Some("rsync://example.test/repo/") + ); + + let added = store + .get_repository_view_entry("rsync://example.test/repo/c.crl") + .expect("get added repo view") + .expect("added entry exists"); + assert_eq!(added.state, crate::repository::storage::RepositoryViewState::Present); +} + +#[test] +fn rrdp_fetch_error_falls_back_to_rsync_without_retry() { + let temp = tempfile::tempdir().expect("tempdir"); + let store_dir = temp.path().join("db"); + let store = RocksStore::open(&store_dir).expect("open rocksdb"); + + let timing = TimingHandle::new(TimingMeta { + recorded_at_utc_rfc3339: "2026-02-28T00:00:00Z".to_string(), + validation_time_utc_rfc3339: "2026-02-28T00:00:00Z".to_string(), + tal_url: None, + db_path: Some(store_dir.to_string_lossy().into_owned()), + }); + + let notification_uri = "https://example.test/notification.xml"; + let published_uri = "rsync://example.test/repo/a.mft"; + let published_bytes = b"x"; + struct AlwaysFailHttp { + notification_calls: AtomicUsize, + } + + impl HttpFetcher for AlwaysFailHttp { + fn fetch(&self, _uri: &str) -> Result, String> { + self.notification_calls.fetch_add(1, Ordering::SeqCst); + Err("http request failed: simulated transient".to_string()) + } + } + + struct SingleObjectRsync { + uri: String, + bytes: Vec, + } + impl RsyncFetcher for SingleObjectRsync { + fn fetch_objects( + &self, + _rsync_base_uri: &str, + ) -> Result)>, RsyncFetchError> { + Ok(vec![(self.uri.clone(), self.bytes.clone())]) + } + } + + let http = AlwaysFailHttp { + notification_calls: AtomicUsize::new(0), + }; + + let policy = Policy { + sync_preference: SyncPreference::RrdpThenRsync, + ..Policy::default() + }; + + let download_log = DownloadLogHandle::new(); + let out = sync_publication_point( + &store, + &policy, + Some(notification_uri), + "rsync://example.test/repo/", + &http, + &SingleObjectRsync { + uri: published_uri.to_string(), + bytes: published_bytes.to_vec(), + }, + Some(&timing), + Some(&download_log), + ) + .expect("sync ok"); + + assert_eq!(out.source, RepoSyncSource::Rsync); + assert_current_object(&store, published_uri, published_bytes); + assert_eq!(http.notification_calls.load(Ordering::SeqCst), 1); + + let events = download_log.snapshot_events(); + assert_eq!(events.len(), 2, "expected 1x notification + 1x rsync"); + assert_eq!( + events + .iter() + .filter(|e| e.kind == AuditDownloadKind::RrdpNotification) + .count(), + 1 + ); + assert_eq!( + events + .iter() + .filter(|e| e.kind == AuditDownloadKind::RrdpNotification && !e.success) + .count(), + 1 + ); + assert_eq!( + events + .iter() + .filter(|e| e.kind == AuditDownloadKind::Rsync) + .count(), + 1 + ); + + let v = timing_to_json(temp.path(), &timing); + let counts = v.get("counts").expect("counts"); + assert_eq!( + counts + .get("rrdp_retry_attempt_total") + .and_then(|v| v.as_u64()), + Some(1) + ); + assert_eq!( + counts + .get("repo_sync_rrdp_failed_total") + .and_then(|v| v.as_u64()), + Some(1) + ); + assert_eq!( + counts + .get("repo_sync_rsync_fallback_ok_total") + .and_then(|v| v.as_u64()), + Some(1) + ); +} diff --git a/src/repository/sync/rrdp.rs b/src/repository/sync/rrdp.rs new file mode 100644 index 0000000..4629023 --- /dev/null +++ b/src/repository/sync/rrdp.rs @@ -0,0 +1,56 @@ +use crate::output::analysis::timing::TimingHandle; +use crate::output::audit::AuditDownloadKind; +use crate::output::audit_downloads::DownloadLogHandle; +use crate::repository::current_repo_index::CurrentRepoIndexHandle; +use crate::repository::storage::{ + RepositoryViewEntry, RepositoryViewState, RocksStore, RrdpDeltaOp, RrdpSourceSyncState, +}; +mod snapshot_apply; + +use crate::repository::sync::store_projection::{ + build_repository_view_present_entry, build_repository_view_withdrawn_entry, + current_rrdp_owner_is, ensure_rrdp_uri_can_be_owned_by, put_repository_view_present, + put_repository_view_withdrawn, put_rrdp_source_member_present, + put_rrdp_source_member_withdrawn, put_rrdp_uri_owner_active, put_rrdp_uri_owner_withdrawn, + update_rrdp_source_record_on_success, upsert_repo_blob_bytes, +}; +use base64::Engine; +use serde::{Deserialize, Serialize}; +use sha2::Digest; +use std::fmt; +use std::io::Write; +use url::Url; +use uuid::Uuid; + +const RRDP_XMLNS: &str = "http://www.ripe.net/rpki/rrdp"; +const RRDP_SNAPSHOT_APPLY_BATCH_SIZE: usize = 1024; + +/// The RRDP object currently being fetched. Keeping this in protocol errors +/// makes an origin rejection actionable without treating it as a transient +/// transport failure. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RrdpResourceKind { + Notification, + Snapshot, + Delta, +} + +impl fmt::Display for RrdpResourceKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::Notification => "notification", + Self::Snapshot => "snapshot", + Self::Delta => "delta", + }) + } +} + +include!("rrdp/models_and_parsing.rs"); +include!("rrdp/snapshot_sync.rs"); +include!("rrdp/notification_sync.rs"); +include!("rrdp/delta.rs"); +include!("rrdp/parse_helpers.rs"); + +#[cfg(test)] +#[path = "rrdp/tests.rs"] +mod tests; diff --git a/src/repository/sync/rrdp/delta.rs b/src/repository/sync/rrdp/delta.rs new file mode 100644 index 0000000..1845c3e --- /dev/null +++ b/src/repository/sync/rrdp/delta.rs @@ -0,0 +1,272 @@ +// Current-repository hydration and delta application. + +fn hydrate_current_repo_index_from_rrdp_members( + store: &RocksStore, + notification_uri: &str, + current_repo_index: Option<&CurrentRepoIndexHandle>, +) -> Result { + let Some(index) = current_repo_index else { + return Ok(0); + }; + + let members = store + .list_current_rrdp_source_members(notification_uri) + .map_err(|e| RrdpSyncError::Storage(e.to_string()))?; + if members.is_empty() { + return Ok(0); + } + + let mut entries = Vec::with_capacity(members.len()); + for member in members { + let current_hash = member.current_hash.ok_or_else(|| { + RrdpSyncError::Storage(format!( + "rrdp source member missing current_hash for current object {}", + member.rsync_uri + )) + })?; + entries.push(RepositoryViewEntry { + rsync_uri: member.rsync_uri, + current_hash: Some(current_hash), + repository_source: Some(notification_uri.to_string()), + object_type: member.object_type, + state: RepositoryViewState::Present, + }); + } + + index + .write() + .map_err(|_| RrdpSyncError::Storage("current repo index lock poisoned".to_string()))? + .apply_repository_view_entries(&entries) + .map_err(RrdpSyncError::Storage)?; + Ok(entries.len()) +} + +fn apply_delta( + store: &RocksStore, + notification_uri: &str, + current_repo_index: Option<&CurrentRepoIndexHandle>, + delta_xml: &[u8], + expected_hash_sha256: [u8; 32], + expected_session_id: Uuid, + expected_serial: u64, +) -> Result { + let computed = sha2::Sha256::digest(delta_xml); + if computed.as_slice() != expected_hash_sha256.as_slice() { + return Err(RrdpError::DeltaHashMismatch.into()); + } + + let delta = parse_delta_file(delta_xml)?; + if delta.session_id != expected_session_id { + return Err(RrdpError::DeltaSessionIdMismatch { + expected: expected_session_id.to_string(), + got: delta.session_id.to_string(), + } + .into()); + } + if delta.serial != expected_serial { + return Err(RrdpError::DeltaSerialMismatch { + expected: expected_serial, + got: delta.serial, + } + .into()); + } + + enum DeltaProjectionEffect { + Upsert { + rsync_uri: String, + bytes: Vec, + }, + Delete { + rsync_uri: String, + previous_hash: String, + }, + } + + let session_id = expected_session_id.to_string(); + let mut ops: Vec = Vec::with_capacity(delta.elements.len()); + let mut projection: Vec = Vec::with_capacity(delta.elements.len()); + for e in delta.elements { + match e { + DeltaElement::Publish { + uri, + hash_sha256: Some(old_hash), + bytes, + } => { + let is_member = store + .is_current_rrdp_source_member(notification_uri, uri.as_str()) + .map_err(|e| RrdpSyncError::Storage(e.to_string()))?; + if !is_member { + return Err(RrdpError::DeltaTargetNotFromRepository { rsync_uri: uri }.into()); + } + ensure_rrdp_uri_can_be_owned_by(store, notification_uri, uri.as_str()) + .map_err(RrdpSyncError::Storage)?; + let old_bytes = store + .load_current_object_bytes_by_uri(uri.as_str()) + .map_err(|e| RrdpSyncError::Storage(e.to_string()))? + .ok_or_else(|| RrdpError::DeltaTargetMissing { + rsync_uri: uri.clone(), + })?; + let old_computed = sha2::Sha256::digest(old_bytes.as_slice()); + if old_computed.as_slice() != old_hash.as_slice() { + return Err(RrdpError::DeltaTargetHashMismatch { rsync_uri: uri }.into()); + } + + ops.push(RrdpDeltaOp::Upsert { + rsync_uri: uri.clone(), + bytes: bytes.clone(), + }); + projection.push(DeltaProjectionEffect::Upsert { + rsync_uri: uri, + bytes, + }); + } + DeltaElement::Publish { + uri, + hash_sha256: None, + bytes, + } => { + let is_member = store + .is_current_rrdp_source_member(notification_uri, uri.as_str()) + .map_err(|e| RrdpSyncError::Storage(e.to_string()))?; + if is_member { + return Err( + RrdpError::DeltaPublishWithoutHashForExisting { rsync_uri: uri }.into(), + ); + } + ensure_rrdp_uri_can_be_owned_by(store, notification_uri, uri.as_str()) + .map_err(RrdpSyncError::Storage)?; + ops.push(RrdpDeltaOp::Upsert { + rsync_uri: uri.clone(), + bytes: bytes.clone(), + }); + projection.push(DeltaProjectionEffect::Upsert { + rsync_uri: uri, + bytes, + }); + } + DeltaElement::Withdraw { uri, hash_sha256 } => { + let is_member = store + .is_current_rrdp_source_member(notification_uri, uri.as_str()) + .map_err(|e| RrdpSyncError::Storage(e.to_string()))?; + if !is_member { + return Err(RrdpError::DeltaTargetNotFromRepository { rsync_uri: uri }.into()); + } + ensure_rrdp_uri_can_be_owned_by(store, notification_uri, uri.as_str()) + .map_err(RrdpSyncError::Storage)?; + let old_bytes = store + .load_current_object_bytes_by_uri(uri.as_str()) + .map_err(|e| RrdpSyncError::Storage(e.to_string()))? + .ok_or_else(|| RrdpError::DeltaTargetMissing { + rsync_uri: uri.clone(), + })?; + let old_computed = sha2::Sha256::digest(old_bytes.as_slice()); + if old_computed.as_slice() != hash_sha256.as_slice() { + return Err(RrdpError::DeltaTargetHashMismatch { rsync_uri: uri }.into()); + } + let previous_hash = hex::encode(old_computed); + ops.push(RrdpDeltaOp::Delete { + rsync_uri: uri.clone(), + }); + projection.push(DeltaProjectionEffect::Delete { + rsync_uri: uri, + previous_hash, + }); + } + } + } + + for effect in projection { + match effect { + DeltaProjectionEffect::Upsert { rsync_uri, bytes } => { + let current_hash = + upsert_repo_blob_bytes(store, &bytes).map_err(RrdpSyncError::Storage)?; + put_repository_view_present(store, notification_uri, &rsync_uri, ¤t_hash) + .map_err(RrdpSyncError::Storage)?; + if let Some(index) = current_repo_index { + let entry = build_repository_view_present_entry( + notification_uri, + &rsync_uri, + ¤t_hash, + ); + index + .write() + .map_err(|_| { + RrdpSyncError::Storage("current repo index lock poisoned".to_string()) + })? + .apply_repository_view_entries(&[entry]) + .map_err(RrdpSyncError::Storage)?; + } + put_rrdp_source_member_present( + store, + notification_uri, + &session_id, + expected_serial, + &rsync_uri, + ¤t_hash, + ) + .map_err(RrdpSyncError::Storage)?; + put_rrdp_uri_owner_active( + store, + notification_uri, + &session_id, + expected_serial, + &rsync_uri, + ¤t_hash, + ) + .map_err(RrdpSyncError::Storage)?; + } + DeltaProjectionEffect::Delete { + rsync_uri, + previous_hash, + } => { + put_rrdp_source_member_withdrawn( + store, + notification_uri, + &session_id, + expected_serial, + &rsync_uri, + Some(previous_hash.clone()), + ) + .map_err(RrdpSyncError::Storage)?; + if current_rrdp_owner_is(store, notification_uri, &rsync_uri) + .map_err(RrdpSyncError::Storage)? + { + put_repository_view_withdrawn( + store, + notification_uri, + &rsync_uri, + Some(previous_hash.clone()), + ) + .map_err(RrdpSyncError::Storage)?; + if let Some(index) = current_repo_index { + let entry = build_repository_view_withdrawn_entry( + notification_uri, + &rsync_uri, + Some(previous_hash.clone()), + ); + index + .write() + .map_err(|_| { + RrdpSyncError::Storage( + "current repo index lock poisoned".to_string(), + ) + })? + .apply_repository_view_entries(&[entry]) + .map_err(RrdpSyncError::Storage)?; + } + put_rrdp_uri_owner_withdrawn( + store, + notification_uri, + &session_id, + expected_serial, + &rsync_uri, + Some(previous_hash), + ) + .map_err(RrdpSyncError::Storage)?; + } + } + } + } + + Ok(ops.len()) +} diff --git a/src/repository/sync/rrdp/models_and_parsing.rs b/src/repository/sync/rrdp/models_and_parsing.rs new file mode 100644 index 0000000..7d33bc8 --- /dev/null +++ b/src/repository/sync/rrdp/models_and_parsing.rs @@ -0,0 +1,639 @@ +// RRDP error/state models and notification/delta parsing. + +#[derive(Debug, thiserror::Error)] +pub enum RrdpError { + #[error("RRDP XML must be US-ASCII encoded (RFC 8182 §3.5.1.3, §3.5.2.3), got non-ASCII bytes")] + NotAscii, + + #[error("RRDP XML parse error: {0} (RFC 8182 §3.5.1.3, §3.5.2.3)")] + Xml(String), + + #[error( + "RRDP root element must be , , or , got <{0}> (RFC 8182 §3.5.1.3, §3.5.2.3, §3.5.3.3)" + )] + UnexpectedRoot(String), + + #[error("RRDP XML namespace must be {RRDP_XMLNS}, got {0} (RFC 8182 §3.5.1.3, §3.5.2.3)")] + InvalidNamespace(String), + + #[error("RRDP version must be 1, got {0} (RFC 8182 §3.5.1.3, §3.5.2.3)")] + InvalidVersion(String), + + #[error("RRDP session_id invalid UUID: {0} (RFC 8182 §3.5.1.3)")] + InvalidSessionId(String), + + #[error("RRDP serial invalid unsigned integer: {0} (RFC 8182 §3.5.1.3)")] + InvalidSerial(String), + + #[error("notification must contain exactly one element (RFC 8182 §3.5.1.3)")] + SnapshotCountInvalid, + + #[error("snapshot/@uri missing (RFC 8182 §3.5.1.3)")] + SnapshotUriMissing, + + #[error("snapshot/@hash missing (RFC 8182 §3.5.1.3)")] + SnapshotHashMissing, + + #[error("snapshot/@hash must be hex encoding of SHA-256, got {0} (RFC 8182 §3.5.1.3)")] + SnapshotHashInvalid(String), + + #[error("delta/@serial missing in notification (RFC 8182 §3.5.1.3)")] + DeltaRefSerialMissing, + + #[error("delta/@uri missing in notification (RFC 8182 §3.5.1.3)")] + DeltaRefUriMissing, + + #[error("delta/@hash missing in notification (RFC 8182 §3.5.1.3)")] + DeltaRefHashMissing, + + #[error("delta/@hash must be hex encoding of SHA-256, got {0} (RFC 8182 §3.5.1.3)")] + DeltaRefHashInvalid(String), + + #[error("delta/@serial duplicates in notification: {0} (RFC 8182 §3.5.1.3)")] + DeltaRefSerialDuplicate(u64), + + #[error( + "delta/@serial must be <= notification/@serial: delta={delta_serial} notification={notification_serial} (RFC 8182 §3.5.1.3)" + )] + DeltaRefSerialTooHigh { + delta_serial: u64, + notification_serial: u64, + }, + + #[error( + "notification contains deltas but does not end at notification/@serial: max_delta={max_delta_serial} notification={notification_serial} (RFC 8182 §3.5.1.3)" + )] + DeltaRefChainDoesNotEndAtNotificationSerial { + max_delta_serial: u64, + notification_serial: u64, + }, + + #[error( + "notification delta chain not contiguous: missing serial={missing_serial} (range {min_serial}..{notification_serial}) (RFC 8182 §3.5.1.3)" + )] + DeltaRefChainNotContiguous { + min_serial: u64, + notification_serial: u64, + missing_serial: u64, + }, + + #[error("snapshot file hash mismatch (RFC 8182 §3.5.1.3)")] + SnapshotHashMismatch, + + #[error("snapshot session_id mismatch: expected {expected}, got {got} (RFC 8182 §3.5.2.3)")] + SnapshotSessionIdMismatch { expected: String, got: String }, + + #[error("snapshot serial mismatch: expected {expected}, got {got} (RFC 8182 §3.5.2.3)")] + SnapshotSerialMismatch { expected: u64, got: u64 }, + + #[error("delta file hash mismatch (RFC 8182 §3.4.2; RFC 8182 §3.5.1.3)")] + DeltaHashMismatch, + + #[error("delta session_id mismatch: expected {expected}, got {got} (RFC 8182 §3.5.3.3)")] + DeltaSessionIdMismatch { expected: String, got: String }, + + #[error("delta serial mismatch: expected {expected}, got {got} (RFC 8182 §3.5.3.3)")] + DeltaSerialMismatch { expected: u64, got: u64 }, + + #[error("notification serial moved backwards: old={old} new={new} (RFC 8182 §3.4.1)")] + NotificationSerialRollback { old: u64, new: u64 }, + + #[error("delta publish without @hash for existing object: {rsync_uri} (RFC 8182 §3.4.2)")] + DeltaPublishWithoutHashForExisting { rsync_uri: String }, + + #[error( + "delta withdraw/replace target not from this repository server: {rsync_uri} (RFC 8182 §3.4.2)" + )] + DeltaTargetNotFromRepository { rsync_uri: String }, + + #[error("delta withdraw/replace target missing in local cache: {rsync_uri} (RFC 8182 §3.4.2)")] + DeltaTargetMissing { rsync_uri: String }, + + #[error("delta withdraw/replace target hash mismatch: {rsync_uri} (RFC 8182 §3.4.2)")] + DeltaTargetHashMismatch { rsync_uri: String }, + + #[error("publish/@uri missing (RFC 8182 §3.5.2.3)")] + PublishUriMissing, + + #[error("publish element missing base64 content (RFC 8182 §3.5.2.3)")] + PublishContentMissing, + + #[error("publish base64 decode failed (RFC 8182 §3.5.2.3): {0}")] + PublishBase64(String), + + #[error("delta file missing @uri (RFC 8182 §3.5.3.3)")] + DeltaPublishUriMissing, + + #[error("delta file base64 content missing (RFC 8182 §3.5.3.3)")] + DeltaPublishContentMissing, + + #[error("delta file base64 decode failed (RFC 8182 §3.5.3.3): {0}")] + DeltaPublishBase64(String), + + #[error( + "delta file @hash must be hex encoding of SHA-256, got {0} (RFC 8182 §3.5.3.3)" + )] + DeltaPublishHashInvalid(String), + + #[error("delta file missing @uri (RFC 8182 §3.5.3.3)")] + DeltaWithdrawUriMissing, + + #[error("delta file missing @hash (RFC 8182 §3.5.3.3)")] + DeltaWithdrawHashMissing, + + #[error( + "delta file @hash must be hex encoding of SHA-256, got {0} (RFC 8182 §3.5.3.3)" + )] + DeltaWithdrawHashInvalid(String), + + #[error("delta file must not contain text content (RFC 8182 §3.5.3.3)")] + DeltaWithdrawUnexpectedContent, + + #[error("delta file must contain at least one publish/withdraw element (RFC 8182 §3.5.3.3)")] + DeltaNoElements, + + #[error("delta file contains unexpected element <{0}> (RFC 8182 §3.5.3.3)")] + DeltaUnexpectedElement(String), + + #[error("invalid RRDP {resource} URI: {detail} (RFC 9674 §3.2)")] + InvalidRrdpUri { + resource: RrdpResourceKind, + detail: String, + }, + + #[error( + "RRDP {resource} URI crosses notification origin: notification={notification_origin}, resource={resource_origin} (RFC 9674 §3.2)" + )] + CrossOriginReference { + resource: RrdpResourceKind, + notification_origin: String, + resource_origin: String, + }, + + #[error( + "RRDP {resource} redirect crosses notification origin: notification={notification_origin}, redirect={redirect_origin} (RFC 9674 §3.2)" + )] + CrossOriginRedirect { + resource: RrdpResourceKind, + notification_origin: String, + redirect_origin: String, + }, + + #[error("RRDP {resource} redirect has no Location header (RFC 9674 §3.2)")] + RedirectLocationMissing { resource: RrdpResourceKind }, + + #[error("RRDP {resource} redirect Location is invalid: {location} (RFC 9674 §3.2)")] + InvalidRedirectLocation { + resource: RrdpResourceKind, + location: String, + }, + + #[error("RRDP {resource} redirect limit exceeded ({max}) (RFC 9674 §3.2)")] + RedirectLimitExceeded { + resource: RrdpResourceKind, + max: usize, + }, +} + +impl RrdpError { + pub fn is_cross_origin_violation(&self) -> bool { + matches!( + self, + Self::CrossOriginReference { .. } | Self::CrossOriginRedirect { .. } + ) + } +} + +/// RFC 9674 §3.2 compares scheme, host name, and port. `Url` normalizes an +/// omitted well-known port, so `https://example/` and `https://example:443/` +/// intentionally have the same effective origin. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RrdpOrigin { + scheme: String, + host: String, + port: u16, +} + +impl fmt::Display for RrdpOrigin { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}://{}:{}", self.scheme, self.host, self.port) + } +} + +impl RrdpOrigin { + pub fn parse(uri: &str, resource: RrdpResourceKind) -> Result { + let url = Url::parse(uri).map_err(|e| RrdpError::InvalidRrdpUri { + resource, + detail: e.to_string(), + })?; + Self::from_url(&url, resource) + } + + pub fn from_url(url: &Url, resource: RrdpResourceKind) -> Result { + if !matches!(url.scheme(), "http" | "https") { + return Err(RrdpError::InvalidRrdpUri { + resource, + detail: format!("unsupported scheme {}", url.scheme()), + }); + } + if !url.username().is_empty() || url.password().is_some() { + return Err(RrdpError::InvalidRrdpUri { + resource, + detail: "userinfo is not permitted".to_string(), + }); + } + let host = url.host_str().ok_or_else(|| RrdpError::InvalidRrdpUri { + resource, + detail: "host is missing".to_string(), + })?; + let port = url + .port_or_known_default() + .ok_or_else(|| RrdpError::InvalidRrdpUri { + resource, + detail: "port is missing or unknown".to_string(), + })?; + Ok(Self { + scheme: url.scheme().to_ascii_lowercase(), + host: host.to_ascii_lowercase(), + port, + }) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum RrdpSyncError { + #[error("{0}")] + Rrdp(#[from] RrdpError), + + #[error("fetch failed: {0}")] + Fetch(String), + + #[error("storage error: {0}")] + Storage(String), +} + +#[derive(Debug, thiserror::Error)] +pub enum RrdpFetchError { + #[error("{0}")] + Rrdp(#[from] RrdpError), + + #[error("{0}")] + Fetch(String), +} + +pub type RrdpSyncResult = Result; + +pub trait Fetcher: Send + Sync { + fn fetch(&self, uri: &str) -> Result, String>; + + fn fetch_to_writer(&self, uri: &str, out: &mut dyn Write) -> Result { + let bytes = self.fetch(uri)?; + out.write_all(&bytes) + .map_err(|e| format!("write sink failed: {e}"))?; + Ok(bytes.len() as u64) + } + + /// RRDP-only fetch path. Implementations that do not perform HTTP may use + /// the compatibility default; HTTP implementations must enforce redirect + /// origin checks before issuing the next request. + fn fetch_rrdp( + &self, + _resource: RrdpResourceKind, + uri: &str, + _notification_origin: &RrdpOrigin, + ) -> Result, RrdpFetchError> { + self.fetch(uri).map_err(RrdpFetchError::Fetch) + } + + fn fetch_rrdp_to_writer( + &self, + _resource: RrdpResourceKind, + uri: &str, + _notification_origin: &RrdpOrigin, + out: &mut dyn Write, + ) -> Result { + self.fetch_to_writer(uri, out) + .map_err(RrdpFetchError::Fetch) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct RrdpState { + pub session_id: String, + pub serial: u64, +} + +impl RrdpState { + pub fn encode(&self) -> Result, String> { + serde_cbor::to_vec(self).map_err(|e| e.to_string()) + } + + pub fn decode(bytes: &[u8]) -> Result { + serde_cbor::from_slice(bytes).map_err(|e| e.to_string()) + } +} + +pub(crate) fn load_rrdp_local_state( + store: &RocksStore, + notification_uri: &str, +) -> Result, String> { + if let Some(record) = store + .get_rrdp_source_record(notification_uri) + .map_err(|e| e.to_string())? + && let (Some(session_id), Some(serial)) = (record.last_session_id, record.last_serial) { + return Ok(Some(RrdpState { session_id, serial })); + } + + Ok(None) +} + +pub(crate) fn persist_rrdp_local_state( + store: &RocksStore, + notification_uri: &str, + state: &RrdpState, + sync_state: RrdpSourceSyncState, + last_snapshot_uri: Option<&str>, + last_snapshot_hash_hex: Option<&str>, +) -> Result<(), String> { + update_rrdp_source_record_on_success( + store, + notification_uri, + state.session_id.as_str(), + state.serial, + sync_state, + last_snapshot_uri, + last_snapshot_hash_hex, + ) +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct NotificationSnapshot { + pub session_id: Uuid, + pub serial: u64, + pub snapshot_uri: String, + pub snapshot_hash_sha256: [u8; 32], +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct NotificationDeltaRef { + pub serial: u64, + pub uri: String, + pub hash_sha256: [u8; 32], +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Notification { + pub session_id: Uuid, + pub serial: u64, + pub snapshot_uri: String, + pub snapshot_hash_sha256: [u8; 32], + /// Deltas referenced by the notification file, sorted by serial ascending. + /// + /// If present, this list is guaranteed to be contiguous and to end at `serial` + /// (RFC 8182 §3.5.1.3). + pub deltas: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum DeltaElement { + Publish { + uri: String, + hash_sha256: Option<[u8; 32]>, + bytes: Vec, + }, + Withdraw { + uri: String, + hash_sha256: [u8; 32], + }, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct DeltaFile { + pub session_id: Uuid, + pub serial: u64, + pub elements: Vec, +} + +pub fn parse_notification(xml: &[u8]) -> Result { + let doc = parse_rrdp_xml(xml)?; + let root = doc.root_element(); + if root.tag_name().name() != "notification" { + return Err(RrdpError::UnexpectedRoot( + root.tag_name().name().to_string(), + )); + } + validate_root_common(&root)?; + + let session_id = parse_uuid_attr(&root, "session_id")?; + let serial = parse_u64_attr(&root, "serial")?; + + let snapshots: Vec<_> = root + .children() + .filter(|n| n.is_element() && n.tag_name().name() == "snapshot") + .collect(); + if snapshots.len() != 1 { + return Err(RrdpError::SnapshotCountInvalid); + } + let snapshot = snapshots[0]; + + let snapshot_uri = snapshot + .attribute("uri") + .ok_or(RrdpError::SnapshotUriMissing)? + .to_string(); + let snapshot_hash_hex = snapshot + .attribute("hash") + .ok_or(RrdpError::SnapshotHashMissing)?; + let snapshot_hash_sha256 = parse_sha256_hex_snapshot(snapshot_hash_hex)?; + + let mut deltas: Vec = Vec::new(); + for d in root + .children() + .filter(|n| n.is_element() && n.tag_name().name() == "delta") + { + let delta_serial = d + .attribute("serial") + .ok_or(RrdpError::DeltaRefSerialMissing)?; + let delta_serial = parse_u64_str(delta_serial)?; + if delta_serial > serial { + return Err(RrdpError::DeltaRefSerialTooHigh { + delta_serial, + notification_serial: serial, + }); + } + let uri = d.attribute("uri").ok_or(RrdpError::DeltaRefUriMissing)?; + let hash = d.attribute("hash").ok_or(RrdpError::DeltaRefHashMissing)?; + let hash_sha256 = parse_sha256_hex_delta_ref(hash)?; + + deltas.push(NotificationDeltaRef { + serial: delta_serial, + uri: uri.to_string(), + hash_sha256, + }); + } + + if !deltas.is_empty() { + let mut seen: std::collections::HashSet = std::collections::HashSet::new(); + let mut min_serial = u64::MAX; + let mut max_serial = 0u64; + for d in &deltas { + if !seen.insert(d.serial) { + return Err(RrdpError::DeltaRefSerialDuplicate(d.serial)); + } + min_serial = min_serial.min(d.serial); + max_serial = max_serial.max(d.serial); + } + if max_serial != serial { + return Err(RrdpError::DeltaRefChainDoesNotEndAtNotificationSerial { + max_delta_serial: max_serial, + notification_serial: serial, + }); + } + for s in min_serial..=serial { + if !seen.contains(&s) { + return Err(RrdpError::DeltaRefChainNotContiguous { + min_serial, + notification_serial: serial, + missing_serial: s, + }); + } + } + deltas.sort_by_key(|d| d.serial); + } + + Ok(Notification { + session_id, + serial, + snapshot_uri, + snapshot_hash_sha256, + deltas, + }) +} + +pub fn parse_notification_snapshot(xml: &[u8]) -> Result { + let n = parse_notification(xml)?; + Ok(NotificationSnapshot { + session_id: n.session_id, + serial: n.serial, + snapshot_uri: n.snapshot_uri, + snapshot_hash_sha256: n.snapshot_hash_sha256, + }) +} + +/// Validate every URI embedded in a notification before reading mutable local +/// RRDP state or fetching a snapshot/delta. RFC 9674 §3.2 requires the +/// notification, snapshot, and all delta URIs to share one origin. +pub fn validate_notification_references( + notification_uri: &str, + notification: &Notification, +) -> Result { + let notification_origin = RrdpOrigin::parse(notification_uri, RrdpResourceKind::Notification)?; + validate_reference_origin( + ¬ification_origin, + ¬ification.snapshot_uri, + RrdpResourceKind::Snapshot, + )?; + for delta in ¬ification.deltas { + validate_reference_origin(¬ification_origin, &delta.uri, RrdpResourceKind::Delta)?; + } + Ok(notification_origin) +} + +fn validate_reference_origin( + notification_origin: &RrdpOrigin, + uri: &str, + resource: RrdpResourceKind, +) -> Result<(), RrdpError> { + let resource_origin = RrdpOrigin::parse(uri, resource)?; + if &resource_origin != notification_origin { + crate::logging::progress::emit!( + "rrdp_cross_origin_rejected", + serde_json::json!({ + "resource": resource.to_string(), + "notification_origin": notification_origin.to_string(), + "resource_origin": resource_origin.to_string(), + "reason": "direct_reference", + "rfc": "RFC 9674 §3.2", + }), + ); + return Err(RrdpError::CrossOriginReference { + resource, + notification_origin: notification_origin.to_string(), + resource_origin: resource_origin.to_string(), + }); + } + Ok(()) +} + +pub fn parse_delta_file(xml: &[u8]) -> Result { + let doc = parse_rrdp_xml(xml)?; + let root = doc.root_element(); + if root.tag_name().name() != "delta" { + return Err(RrdpError::UnexpectedRoot( + root.tag_name().name().to_string(), + )); + } + validate_root_common(&root)?; + + let session_id = parse_uuid_attr(&root, "session_id")?; + let serial = parse_u64_attr(&root, "serial")?; + + let mut elements: Vec = Vec::new(); + for child in root.children().filter(|n| n.is_element()) { + match child.tag_name().name() { + "publish" => { + let uri = child + .attribute("uri") + .ok_or(RrdpError::DeltaPublishUriMissing)? + .to_string(); + let hash_sha256 = child + .attribute("hash") + .map(parse_sha256_hex_delta_publish) + .transpose()?; + + let content_b64 = + collect_element_text(&child).ok_or(RrdpError::DeltaPublishContentMissing)?; + let content_b64 = strip_all_ascii_whitespace(&content_b64); + if content_b64.is_empty() { + return Err(RrdpError::DeltaPublishContentMissing); + } + let bytes = base64::engine::general_purpose::STANDARD + .decode(content_b64.as_bytes()) + .map_err(|e| RrdpError::DeltaPublishBase64(e.to_string()))?; + + elements.push(DeltaElement::Publish { + uri, + hash_sha256, + bytes, + }); + } + "withdraw" => { + let uri = child + .attribute("uri") + .ok_or(RrdpError::DeltaWithdrawUriMissing)? + .to_string(); + let hash = child + .attribute("hash") + .ok_or(RrdpError::DeltaWithdrawHashMissing)?; + let hash_sha256 = parse_sha256_hex_delta_withdraw(hash)?; + + if let Some(s) = collect_element_text(&child) + && !strip_all_ascii_whitespace(&s).is_empty() { + return Err(RrdpError::DeltaWithdrawUnexpectedContent); + } + + elements.push(DeltaElement::Withdraw { uri, hash_sha256 }); + } + other => return Err(RrdpError::DeltaUnexpectedElement(other.to_string())), + } + } + + if elements.is_empty() { + return Err(RrdpError::DeltaNoElements); + } + + Ok(DeltaFile { + session_id, + serial, + elements, + }) +} diff --git a/src/repository/sync/rrdp/notification_sync.rs b/src/repository/sync/rrdp/notification_sync.rs new file mode 100644 index 0000000..743a0d3 --- /dev/null +++ b/src/repository/sync/rrdp/notification_sync.rs @@ -0,0 +1,375 @@ +// RRDP notification, delta-chain, and state synchronization. + +pub fn sync_from_notification( + store: &RocksStore, + notification_uri: &str, + notification_xml: &[u8], + fetcher: &dyn Fetcher, +) -> RrdpSyncResult { + sync_from_notification_inner( + store, + notification_uri, + None, + notification_xml, + fetcher, + None, + None, + ) +} + +pub fn sync_from_notification_with_timing( + store: &RocksStore, + notification_uri: &str, + notification_xml: &[u8], + fetcher: &dyn Fetcher, + timing: Option<&TimingHandle>, +) -> RrdpSyncResult { + sync_from_notification_inner( + store, + notification_uri, + None, + notification_xml, + fetcher, + timing, + None, + ) +} + +pub fn sync_from_notification_with_timing_and_download_log( + store: &RocksStore, + notification_uri: &str, + current_repo_index: Option<&CurrentRepoIndexHandle>, + notification_xml: &[u8], + fetcher: &dyn Fetcher, + timing: Option<&TimingHandle>, + download_log: Option<&DownloadLogHandle>, +) -> RrdpSyncResult { + sync_from_notification_inner( + store, + notification_uri, + current_repo_index, + notification_xml, + fetcher, + timing, + download_log, + ) +} + +fn sync_from_notification_inner( + store: &RocksStore, + notification_uri: &str, + current_repo_index: Option<&CurrentRepoIndexHandle>, + notification_xml: &[u8], + fetcher: &dyn Fetcher, + timing: Option<&TimingHandle>, + download_log: Option<&DownloadLogHandle>, +) -> RrdpSyncResult { + let _parse_step = timing + .as_ref() + .map(|t| t.span_rrdp_repo_step(notification_uri, "parse_notification")); + let _parse_total = timing + .as_ref() + .map(|t| t.span_phase("rrdp_parse_notification_total")); + let notif = parse_notification(notification_xml)?; + let notification_origin = match validate_notification_references(notification_uri, ¬if) { + Ok(origin) => origin, + Err(e) => { + if let Some(t) = timing.as_ref() { + t.record_count( + "rrdp_rejected_cross_origin_total", + e.is_cross_origin_violation() as u64, + ); + } + return Err(e.into()); + } + }; + drop(_parse_step); + drop(_parse_total); + if let Some(t) = timing.as_ref() { + t.record_count( + "rrdp_notification_delta_refs_total", + notif.deltas.len() as u64, + ); + } + + let _read_state_step = timing + .as_ref() + .map(|t| t.span_rrdp_repo_step(notification_uri, "read_state")); + let _read_state_total = timing + .as_ref() + .map(|t| t.span_phase("rrdp_read_state_total")); + let state = load_rrdp_local_state(store, notification_uri).map_err(RrdpSyncError::Storage)?; + drop(_read_state_step); + drop(_read_state_total); + + let same_session_state = state + .as_ref() + .filter(|s| s.session_id == notif.session_id.to_string()); + + if let Some(s) = same_session_state { + if s.serial == notif.serial { + let _hydrate_step = timing + .as_ref() + .map(|t| t.span_rrdp_repo_step(notification_uri, "hydrate_current_index")); + let _hydrate_total = timing + .as_ref() + .map(|t| t.span_phase("rrdp_hydrate_current_index_total")); + let hydrated = hydrate_current_repo_index_from_rrdp_members( + store, + notification_uri, + current_repo_index, + )?; + drop(_hydrate_step); + drop(_hydrate_total); + if let Some(t) = timing.as_ref() { + t.record_count("rrdp_current_index_hydrated_objects_total", hydrated as u64); + } + return Ok(0); + } + if s.serial > notif.serial { + return Err(RrdpError::NotificationSerialRollback { + old: s.serial, + new: notif.serial, + } + .into()); + } + } + + if let Some(s) = same_session_state { + // RFC 8182 §3.4.1: if session matches, MAY use deltas when a contiguous chain from the + // last processed serial to the current serial can be processed (i.e. deltas cover the gap). + let want_first = s.serial + 1; + let want_last = notif.serial; + + if want_first <= want_last && !notif.deltas.is_empty() { + let min_serial = notif.deltas[0].serial; + let max_serial = notif.deltas[notif.deltas.len() - 1].serial; + + // `parse_notification` guarantees contiguity and max==notif.serial when deltas exist. + if max_serial == notif.serial && want_first >= min_serial { + // Fetch all required delta files first so a network failure doesn't leave us with + // partially applied deltas and no snapshot fallback. + let _fetch_d_step = timing + .as_ref() + .map(|t| t.span_rrdp_repo_step(notification_uri, "fetch_deltas")); + let _fetch_d_total = timing + .as_ref() + .map(|t| t.span_phase("rrdp_fetch_deltas_total")); + let mut fetched: Vec<(u64, [u8; 32], Vec)> = + Vec::with_capacity((want_last - want_first + 1) as usize); + let mut fetch_ok = true; + for serial in want_first..=want_last { + if let Some(t) = timing.as_ref() { + t.record_count("rrdp_delta_fetch_attempted_total", 1); + } + let idx = (serial - min_serial) as usize; + let dref = match notif.deltas.get(idx) { + Some(v) if v.serial == serial => v, + _ => { + fetch_ok = false; + break; + } + }; + + let mut dl_span = download_log + .map(|dl| dl.span_download(AuditDownloadKind::RrdpDelta, &dref.uri)); + match fetcher.fetch_rrdp( + RrdpResourceKind::Delta, + &dref.uri, + ¬ification_origin, + ) { + Ok(bytes) => { + if let Some(t) = timing.as_ref() { + t.record_count("rrdp_delta_fetch_ok_total", 1); + t.record_count("rrdp_delta_bytes_total", bytes.len() as u64); + } + if let Some(s) = dl_span.as_mut() { + s.set_bytes(bytes.len() as u64); + s.set_ok(); + } + fetched.push((serial, dref.hash_sha256, bytes)) + } + Err(RrdpFetchError::Fetch(e)) => { + if let Some(t) = timing.as_ref() { + t.record_count("rrdp_delta_fetch_fail_total", 1); + } + if let Some(s) = dl_span.as_mut() { + s.set_err(e.clone()); + } + fetch_ok = false; + break; + } + Err(RrdpFetchError::Rrdp(e)) => return Err(e.into()), + } + } + drop(_fetch_d_step); + drop(_fetch_d_total); + + if fetch_ok { + let _apply_d_step = timing + .as_ref() + .map(|t| t.span_rrdp_repo_step(notification_uri, "apply_deltas")); + let _apply_d_total = timing + .as_ref() + .map(|t| t.span_phase("rrdp_apply_deltas_total")); + let mut applied_total = 0usize; + let mut ok = true; + for (serial, expected_hash, bytes) in &fetched { + match apply_delta( + store, + notification_uri, + current_repo_index, + bytes.as_slice(), + *expected_hash, + notif.session_id, + *serial, + ) { + Ok(n) => applied_total += n, + Err(_) => { + ok = false; + break; + } + } + } + drop(_apply_d_step); + drop(_apply_d_total); + + if ok { + let _write_state_step = timing + .as_ref() + .map(|t| t.span_rrdp_repo_step(notification_uri, "write_state")); + let _write_state_total = timing + .as_ref() + .map(|t| t.span_phase("rrdp_write_state_total")); + let new_state = RrdpState { + session_id: notif.session_id.to_string(), + serial: notif.serial, + }; + persist_rrdp_local_state( + store, + notification_uri, + &new_state, + RrdpSourceSyncState::DeltaReady, + Some(¬if.snapshot_uri), + Some(&hex::encode(notif.snapshot_hash_sha256)), + ) + .map_err(RrdpSyncError::Storage)?; + drop(_write_state_step); + drop(_write_state_total); + if let Some(t) = timing.as_ref() { + t.record_count("rrdp_delta_ops_applied_total", applied_total as u64); + } + let _hydrate_step = timing.as_ref().map(|t| { + t.span_rrdp_repo_step(notification_uri, "hydrate_current_index") + }); + let _hydrate_total = timing + .as_ref() + .map(|t| t.span_phase("rrdp_hydrate_current_index_total")); + let hydrated = hydrate_current_repo_index_from_rrdp_members( + store, + notification_uri, + current_repo_index, + )?; + drop(_hydrate_step); + drop(_hydrate_total); + if let Some(t) = timing.as_ref() { + t.record_count( + "rrdp_current_index_hydrated_objects_total", + hydrated as u64, + ); + } + return Ok(applied_total); + } + } + } + } + } + + // Snapshot fallback (RFC 8182 §3.4.3). + let _fetch_step = timing + .as_ref() + .map(|t| t.span_rrdp_repo_step(notification_uri, "fetch_snapshot")); + let _fetch_total = timing + .as_ref() + .map(|t| t.span_phase("rrdp_fetch_snapshot_total")); + let mut dl_span = download_log + .map(|dl| dl.span_download(AuditDownloadKind::RrdpSnapshot, ¬if.snapshot_uri)); + let (snapshot_file, _snapshot_bytes) = match fetch_snapshot_into_tempfile( + fetcher, + ¬if.snapshot_uri, + ¬if.snapshot_hash_sha256, + ¬ification_origin, + ) { + Ok(v) => { + if let Some(t) = timing.as_ref() { + t.record_count("rrdp_snapshot_fetch_ok_total", 1); + t.record_count("rrdp_snapshot_bytes_total", v.1); + } + if let Some(s) = dl_span.as_mut() { + s.set_bytes(v.1); + s.set_ok(); + } + v + } + Err(RrdpSyncError::Fetch(e)) => { + if let Some(t) = timing.as_ref() { + t.record_count("rrdp_snapshot_fetch_fail_total", 1); + } + if let Some(s) = dl_span.as_mut() { + s.set_err(e.clone()); + } + return Err(RrdpSyncError::Fetch(e)); + } + Err(e) => return Err(e), + }; + drop(_fetch_step); + drop(_fetch_total); + + let _apply_step = timing + .as_ref() + .map(|t| t.span_rrdp_repo_step(notification_uri, "apply_snapshot")); + let _apply_total = timing + .as_ref() + .map(|t| t.span_phase("rrdp_apply_snapshot_total")); + let published = apply_snapshot_from_bufread( + store, + notification_uri, + current_repo_index, + std::io::BufReader::new( + snapshot_file + .reopen() + .map_err(|e| RrdpSyncError::Fetch(format!("tempfile reopen failed: {e}")))?, + ), + notif.session_id, + notif.serial, + )?; + if let Some(t) = timing.as_ref() { + t.record_count("rrdp_snapshot_objects_applied_total", published as u64); + } + drop(_apply_step); + drop(_apply_total); + + let _write_state_step = timing + .as_ref() + .map(|t| t.span_rrdp_repo_step(notification_uri, "write_state")); + let _write_state_total = timing + .as_ref() + .map(|t| t.span_phase("rrdp_write_state_total")); + let new_state = RrdpState { + session_id: notif.session_id.to_string(), + serial: notif.serial, + }; + persist_rrdp_local_state( + store, + notification_uri, + &new_state, + RrdpSourceSyncState::SnapshotOnly, + Some(¬if.snapshot_uri), + Some(&hex::encode(notif.snapshot_hash_sha256)), + ) + .map_err(RrdpSyncError::Storage)?; + drop(_write_state_step); + drop(_write_state_total); + + Ok(published) +} diff --git a/src/repository/sync/rrdp/parse_helpers.rs b/src/repository/sync/rrdp/parse_helpers.rs new file mode 100644 index 0000000..af97de6 --- /dev/null +++ b/src/repository/sync/rrdp/parse_helpers.rs @@ -0,0 +1,87 @@ +// Shared XML, URI, and hash parsing helpers. + +#[cfg(test)] +use snapshot_apply::apply_snapshot; +use snapshot_apply::{apply_snapshot_from_bufread, fetch_snapshot_into_tempfile}; + +fn parse_rrdp_xml(xml: &[u8]) -> Result, RrdpError> { + if xml.iter().any(|&b| b > 0x7F) { + return Err(RrdpError::NotAscii); + } + let s = std::str::from_utf8(xml).map_err(|e| RrdpError::Xml(e.to_string()))?; + roxmltree::Document::parse(s).map_err(|e| RrdpError::Xml(e.to_string())) +} + +fn validate_root_common(root: &roxmltree::Node<'_, '_>) -> Result<(), RrdpError> { + let ns = root.default_namespace().unwrap_or("").to_string(); + if ns != RRDP_XMLNS { + return Err(RrdpError::InvalidNamespace(ns)); + } + + let version = root.attribute("version").unwrap_or(""); + if version != "1" { + return Err(RrdpError::InvalidVersion(version.to_string())); + } + + Ok(()) +} + +fn parse_uuid_attr(root: &roxmltree::Node<'_, '_>, name: &'static str) -> Result { + let s = root.attribute(name).unwrap_or(""); + Uuid::parse_str(s).map_err(|_e| RrdpError::InvalidSessionId(s.to_string())) +} + +fn parse_u64_attr(root: &roxmltree::Node<'_, '_>, name: &'static str) -> Result { + let s = root.attribute(name).unwrap_or(""); + parse_u64_str(s) +} + +fn parse_u64_str(s: &str) -> Result { + let v = s + .parse::() + .map_err(|_e| RrdpError::InvalidSerial(s.to_string()))?; + if v == 0 { + return Err(RrdpError::InvalidSerial(s.to_string())); + } + Ok(v) +} + +fn parse_sha256_hex_impl(s: &str, invalid: fn(String) -> RrdpError) -> Result<[u8; 32], RrdpError> { + let bytes = hex::decode(s).map_err(|_e| invalid(s.to_string()))?; + if bytes.len() != 32 { + return Err(invalid(s.to_string())); + } + let mut out = [0u8; 32]; + out.copy_from_slice(&bytes); + Ok(out) +} + +fn parse_sha256_hex_snapshot(s: &str) -> Result<[u8; 32], RrdpError> { + parse_sha256_hex_impl(s, RrdpError::SnapshotHashInvalid) +} + +fn parse_sha256_hex_delta_ref(s: &str) -> Result<[u8; 32], RrdpError> { + parse_sha256_hex_impl(s, RrdpError::DeltaRefHashInvalid) +} + +fn parse_sha256_hex_delta_publish(s: &str) -> Result<[u8; 32], RrdpError> { + parse_sha256_hex_impl(s, RrdpError::DeltaPublishHashInvalid) +} + +fn parse_sha256_hex_delta_withdraw(s: &str) -> Result<[u8; 32], RrdpError> { + parse_sha256_hex_impl(s, RrdpError::DeltaWithdrawHashInvalid) +} + +fn collect_element_text(node: &roxmltree::Node<'_, '_>) -> Option { + let mut out = String::new(); + for child in node.children() { + if child.is_text() { + out.push_str(child.text().unwrap_or("")); + } + } + if out.is_empty() { None } else { Some(out) } +} + +fn strip_all_ascii_whitespace(s: &str) -> String { + s.chars().filter(|c| !c.is_ascii_whitespace()).collect() +} diff --git a/src/repository/sync/rrdp/snapshot_apply.rs b/src/repository/sync/rrdp/snapshot_apply.rs new file mode 100644 index 0000000..0d20171 --- /dev/null +++ b/src/repository/sync/rrdp/snapshot_apply.rs @@ -0,0 +1,454 @@ +use base64::Engine; +use quick_xml::Reader; +use quick_xml::events::Event; +use sha2::Digest; +use std::io::{BufRead, Seek, SeekFrom, Write}; +use uuid::Uuid; + +use crate::repository::current_repo_index::CurrentRepoIndexHandle; +use crate::repository::storage::RocksStore; +use crate::repository::sync::store_projection::{ + build_repository_view_present_entry, build_repository_view_withdrawn_entry, + build_rrdp_source_member_present_record, build_rrdp_source_member_withdrawn_record, + build_rrdp_uri_owner_active_record, build_rrdp_uri_owner_withdrawn_record, compute_sha256_hex, + current_rrdp_owner_is, ensure_rrdp_uri_can_be_owned_by, prepare_repo_bytes_batch, +}; + +use super::{ + Fetcher, RRDP_SNAPSHOT_APPLY_BATCH_SIZE, RRDP_XMLNS, RrdpError, RrdpFetchError, RrdpOrigin, + RrdpResourceKind, RrdpSyncError, parse_u64_str, strip_all_ascii_whitespace, +}; + +#[cfg(test)] +pub(super) fn apply_snapshot( + store: &RocksStore, + notification_uri: &str, + current_repo_index: Option<&CurrentRepoIndexHandle>, + snapshot_xml: &[u8], + expected_session_id: Uuid, + expected_serial: u64, +) -> Result { + if snapshot_xml.iter().any(|&b| b > 0x7F) { + return Err(RrdpError::NotAscii.into()); + } + apply_snapshot_from_bufread( + store, + notification_uri, + current_repo_index, + std::io::Cursor::new(snapshot_xml), + expected_session_id, + expected_serial, + ) +} + +pub(super) fn apply_snapshot_from_bufread( + store: &RocksStore, + notification_uri: &str, + current_repo_index: Option<&CurrentRepoIndexHandle>, + input: R, + expected_session_id: Uuid, + expected_serial: u64, +) -> Result { + let previous_members: Vec = store + .list_current_rrdp_source_members(notification_uri) + .map_err(|e| RrdpSyncError::Storage(e.to_string()))? + .into_iter() + .map(|record| record.rsync_uri) + .collect(); + let session_id = expected_session_id.to_string(); + let mut new_set: std::collections::HashSet = std::collections::HashSet::new(); + let mut batch_published: Vec<(String, Vec)> = + Vec::with_capacity(RRDP_SNAPSHOT_APPLY_BATCH_SIZE); + let mut published_count = 0usize; + + let mut reader = Reader::from_reader(input); + reader.config_mut().trim_text(false); + let mut buf = Vec::new(); + let mut root_seen = false; + let mut in_publish = false; + let mut publish_nested_depth = 0usize; + let mut current_publish_uri: Option = None; + let mut current_publish_text = String::new(); + + loop { + match reader.read_event_into(&mut buf) { + Ok(Event::Start(e)) => { + let local_name = e.local_name(); + let local_name = local_name.as_ref(); + if !root_seen { + root_seen = true; + if local_name != b"snapshot" { + let got = String::from_utf8_lossy(local_name).to_string(); + return Err(RrdpError::UnexpectedRoot(got).into()); + } + let mut xmlns = String::new(); + let mut version = String::new(); + let mut session_id_attr = String::new(); + let mut serial_attr = String::new(); + for attr in e.attributes().with_checks(false) { + let attr = match attr { + Ok(attr) => attr, + Err(e) => return Err(RrdpError::Xml(e.to_string()).into()), + }; + let key = attr.key.as_ref(); + let value = attr + .decode_and_unescape_value(reader.decoder()) + .map_err(|e| RrdpError::Xml(e.to_string()))? + .into_owned(); + match key { + b"xmlns" => xmlns = value, + b"version" => version = value, + b"session_id" => session_id_attr = value, + b"serial" => serial_attr = value, + _ => {} + } + } + if xmlns != RRDP_XMLNS { + return Err(RrdpError::InvalidNamespace(xmlns).into()); + } + if version != "1" { + return Err(RrdpError::InvalidVersion(version).into()); + } + let got_session_id = Uuid::parse_str(&session_id_attr) + .map_err(|_| RrdpError::InvalidSessionId(session_id_attr.clone()))?; + if got_session_id != expected_session_id { + return Err(RrdpError::SnapshotSessionIdMismatch { + expected: expected_session_id.to_string(), + got: got_session_id.to_string(), + } + .into()); + } + let got_serial = parse_u64_str(&serial_attr)?; + if got_serial != expected_serial { + return Err(RrdpError::SnapshotSerialMismatch { + expected: expected_serial, + got: got_serial, + } + .into()); + } + } else if in_publish { + publish_nested_depth += 1; + } else if local_name == b"publish" { + let mut uri = None; + for attr in e.attributes().with_checks(false) { + let attr = match attr { + Ok(attr) => attr, + Err(e) => return Err(RrdpError::Xml(e.to_string()).into()), + }; + if attr.key.as_ref() == b"uri" { + uri = Some( + attr.decode_and_unescape_value(reader.decoder()) + .map_err(|e| RrdpError::Xml(e.to_string()))? + .into_owned(), + ); + } + } + let uri = uri.ok_or(RrdpError::PublishUriMissing)?; + ensure_rrdp_uri_can_be_owned_by(store, notification_uri, &uri) + .map_err(RrdpSyncError::Storage)?; + in_publish = true; + publish_nested_depth = 0; + current_publish_uri = Some(uri); + current_publish_text.clear(); + } + } + Ok(Event::Empty(e)) => { + let local_name = e.local_name(); + let local_name = local_name.as_ref(); + if !root_seen { + let got = String::from_utf8_lossy(local_name).to_string(); + return Err(RrdpError::UnexpectedRoot(got).into()); + } + if local_name == b"publish" { + let mut has_uri = false; + for attr in e.attributes().with_checks(false) { + let attr = match attr { + Ok(attr) => attr, + Err(e) => return Err(RrdpError::Xml(e.to_string()).into()), + }; + if attr.key.as_ref() == b"uri" { + has_uri = true; + break; + } + } + if !has_uri { + return Err(RrdpError::PublishUriMissing.into()); + } + return Err(RrdpError::PublishContentMissing.into()); + } + } + Ok(Event::Text(e)) => { + if in_publish && publish_nested_depth == 0 { + let text = reader + .decoder() + .decode(e.as_ref()) + .map_err(|e| RrdpError::Xml(e.to_string()))?; + current_publish_text.push_str(&text); + } + } + Ok(Event::CData(e)) => { + if in_publish && publish_nested_depth == 0 { + let text = reader + .decoder() + .decode(e.as_ref()) + .map_err(|e| RrdpError::Xml(e.to_string()))?; + current_publish_text.push_str(&text); + } + } + Ok(Event::End(e)) => { + let local_name = e.local_name(); + let local_name = local_name.as_ref(); + if in_publish { + if publish_nested_depth > 0 { + publish_nested_depth -= 1; + } else if local_name == b"publish" { + let uri = current_publish_uri + .take() + .ok_or_else(|| RrdpError::Xml("publish uri missing in state".into()))?; + let content_b64 = strip_all_ascii_whitespace(¤t_publish_text); + current_publish_text.clear(); + if content_b64.is_empty() { + return Err(RrdpError::PublishContentMissing.into()); + } + let bytes = base64::engine::general_purpose::STANDARD + .decode(content_b64.as_bytes()) + .map_err(|e| RrdpError::PublishBase64(e.to_string()))?; + new_set.insert(uri.clone()); + batch_published.push((uri, bytes)); + published_count += 1; + if batch_published.len() >= RRDP_SNAPSHOT_APPLY_BATCH_SIZE { + flush_snapshot_publish_batch( + store, + notification_uri, + current_repo_index, + &session_id, + expected_serial, + &batch_published, + )?; + batch_published.clear(); + } + in_publish = false; + } + } + } + Ok(Event::Eof) => break, + Ok(Event::Decl(_) | Event::PI(_) | Event::Comment(_) | Event::DocType(_)) => {} + Err(e) => return Err(RrdpError::Xml(e.to_string()).into()), + } + buf.clear(); + } + + if !root_seen { + return Err(RrdpError::Xml("missing root element".to_string()).into()); + } + if in_publish { + return Err(RrdpError::PublishContentMissing.into()); + } + if !batch_published.is_empty() { + flush_snapshot_publish_batch( + store, + notification_uri, + current_repo_index, + &session_id, + expected_serial, + &batch_published, + )?; + batch_published.clear(); + } + + let mut withdrawn: Vec<(String, Option)> = Vec::new(); + for old_uri in &previous_members { + if new_set.contains(old_uri) { + continue; + } + let previous_hash = store + .get_repository_view_entry(old_uri) + .map_err(|e| RrdpSyncError::Storage(e.to_string()))? + .and_then(|entry| entry.current_hash) + .or_else(|| { + store + .load_current_object_bytes_by_uri(old_uri) + .ok() + .flatten() + .map(|bytes| compute_sha256_hex(&bytes)) + }); + withdrawn.push((old_uri.clone(), previous_hash)); + } + + let mut repository_view_entries = Vec::with_capacity(withdrawn.len()); + let mut member_records = Vec::with_capacity(withdrawn.len()); + let mut owner_records = Vec::with_capacity(withdrawn.len()); + for (uri, previous_hash) in withdrawn { + member_records.push(build_rrdp_source_member_withdrawn_record( + notification_uri, + &session_id, + expected_serial, + &uri, + previous_hash.clone(), + )); + if current_rrdp_owner_is(store, notification_uri, &uri).map_err(RrdpSyncError::Storage)? { + repository_view_entries.push(build_repository_view_withdrawn_entry( + notification_uri, + &uri, + previous_hash.clone(), + )); + owner_records.push(build_rrdp_uri_owner_withdrawn_record( + notification_uri, + &session_id, + expected_serial, + &uri, + previous_hash, + )); + } + } + store + .put_projection_batch(&repository_view_entries, &member_records, &owner_records) + .map_err(|e| RrdpSyncError::Storage(e.to_string()))?; + if let Some(index) = current_repo_index { + index + .write() + .map_err(|_| RrdpSyncError::Storage("current repo index lock poisoned".to_string()))? + .apply_repository_view_entries(&repository_view_entries) + .map_err(RrdpSyncError::Storage)?; + } + + Ok(published_count) +} + +fn flush_snapshot_publish_batch( + store: &RocksStore, + notification_uri: &str, + current_repo_index: Option<&CurrentRepoIndexHandle>, + session_id: &str, + serial: u64, + published: &[(String, Vec)], +) -> Result<(), RrdpSyncError> { + let prepared_bytes = prepare_repo_bytes_batch(published).map_err(RrdpSyncError::Storage)?; + let mut repository_view_entries = Vec::with_capacity(published.len()); + let mut member_records = Vec::with_capacity(published.len()); + let mut owner_records = Vec::with_capacity(published.len()); + + for (uri, _bytes) in published { + let current_hash = prepared_bytes + .uri_to_hash + .get(uri) + .cloned() + .ok_or_else(|| { + RrdpSyncError::Storage(format!("missing raw_by_hash mapping for {uri}")) + })?; + repository_view_entries.push(build_repository_view_present_entry( + notification_uri, + uri, + ¤t_hash, + )); + member_records.push(build_rrdp_source_member_present_record( + notification_uri, + session_id, + serial, + uri, + ¤t_hash, + )); + owner_records.push(build_rrdp_uri_owner_active_record( + notification_uri, + session_id, + serial, + uri, + ¤t_hash, + )); + } + + store + .put_blob_bytes_batch(&prepared_bytes.blobs_to_write) + .map_err(|e| RrdpSyncError::Storage(e.to_string()))?; + store + .put_projection_batch(&repository_view_entries, &member_records, &owner_records) + .map_err(|e| RrdpSyncError::Storage(e.to_string()))?; + if let Some(index) = current_repo_index { + index + .write() + .map_err(|_| RrdpSyncError::Storage("current repo index lock poisoned".to_string()))? + .apply_repository_view_entries(&repository_view_entries) + .map_err(RrdpSyncError::Storage)?; + } + + Ok(()) +} + +const SNAPSHOT_NON_ASCII_ERROR: &str = "snapshot body contains non-ASCII bytes"; + +struct SnapshotSpoolWriter<'a, W: Write> { + inner: &'a mut W, + hasher: sha2::Sha256, + bytes: u64, +} + +impl<'a, W: Write> SnapshotSpoolWriter<'a, W> { + fn new(inner: &'a mut W) -> Self { + Self { + inner, + hasher: sha2::Sha256::new(), + bytes: 0, + } + } + + fn finalize_hash(self) -> [u8; 32] { + let digest = self.hasher.finalize(); + let mut out = [0u8; 32]; + out.copy_from_slice(&digest); + out + } +} + +impl Write for SnapshotSpoolWriter<'_, W> { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + if buf.iter().any(|&b| b > 0x7F) { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + SNAPSHOT_NON_ASCII_ERROR, + )); + } + let n = self.inner.write(buf)?; + self.hasher.update(&buf[..n]); + self.bytes += n as u64; + Ok(n) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.inner.flush() + } +} + +pub(super) fn fetch_snapshot_into_tempfile( + fetcher: &dyn Fetcher, + snapshot_uri: &str, + expected_hash_sha256: &[u8; 32], + notification_origin: &RrdpOrigin, +) -> Result<(tempfile::NamedTempFile, u64), RrdpSyncError> { + let mut tmp = tempfile::NamedTempFile::new() + .map_err(|e| RrdpSyncError::Fetch(format!("tempfile create failed: {e}")))?; + let mut spool = SnapshotSpoolWriter::new(tmp.as_file_mut()); + let bytes_written = match fetcher.fetch_rrdp_to_writer( + RrdpResourceKind::Snapshot, + snapshot_uri, + notification_origin, + &mut spool, + ) { + Ok(bytes) => bytes, + Err(RrdpFetchError::Fetch(e)) if e.contains(SNAPSHOT_NON_ASCII_ERROR) => { + return Err(RrdpError::NotAscii.into()); + } + Err(RrdpFetchError::Fetch(e)) => return Err(RrdpSyncError::Fetch(e)), + Err(RrdpFetchError::Rrdp(e)) => return Err(e.into()), + }; + let computed = spool.finalize_hash(); + if computed.as_slice() != expected_hash_sha256.as_slice() { + return Err(RrdpError::SnapshotHashMismatch.into()); + } + tmp.as_file_mut() + .flush() + .map_err(|e| RrdpSyncError::Fetch(format!("tempfile flush failed: {e}")))?; + tmp.as_file_mut() + .seek(SeekFrom::Start(0)) + .map_err(|e| RrdpSyncError::Fetch(format!("tempfile rewind failed: {e}")))?; + Ok((tmp, bytes_written)) +} diff --git a/src/repository/sync/rrdp/snapshot_sync.rs b/src/repository/sync/rrdp/snapshot_sync.rs new file mode 100644 index 0000000..2c86de7 --- /dev/null +++ b/src/repository/sync/rrdp/snapshot_sync.rs @@ -0,0 +1,170 @@ +// RRDP snapshot synchronization and timing instrumentation. + +pub fn sync_from_notification_snapshot( + store: &RocksStore, + notification_uri: &str, + notification_xml: &[u8], + fetcher: &dyn Fetcher, +) -> RrdpSyncResult { + sync_from_notification_snapshot_inner( + store, + notification_uri, + notification_xml, + fetcher, + None, + None, + ) +} + +pub fn sync_from_notification_snapshot_with_timing( + store: &RocksStore, + notification_uri: &str, + notification_xml: &[u8], + fetcher: &dyn Fetcher, + timing: Option<&TimingHandle>, +) -> RrdpSyncResult { + sync_from_notification_snapshot_inner( + store, + notification_uri, + notification_xml, + fetcher, + timing, + None, + ) +} + +pub fn sync_from_notification_snapshot_with_timing_and_download_log( + store: &RocksStore, + notification_uri: &str, + notification_xml: &[u8], + fetcher: &dyn Fetcher, + timing: Option<&TimingHandle>, + download_log: Option<&DownloadLogHandle>, +) -> RrdpSyncResult { + sync_from_notification_snapshot_inner( + store, + notification_uri, + notification_xml, + fetcher, + timing, + download_log, + ) +} + +fn sync_from_notification_snapshot_inner( + store: &RocksStore, + notification_uri: &str, + notification_xml: &[u8], + fetcher: &dyn Fetcher, + timing: Option<&TimingHandle>, + download_log: Option<&DownloadLogHandle>, +) -> RrdpSyncResult { + let _parse_step = timing + .as_ref() + .map(|t| t.span_rrdp_repo_step(notification_uri, "parse_notification_snapshot")); + let _parse_total = timing + .as_ref() + .map(|t| t.span_phase("rrdp_parse_notification_total")); + let notif = parse_notification(notification_xml)?; + let notification_origin = match validate_notification_references(notification_uri, ¬if) { + Ok(origin) => origin, + Err(e) => { + if let Some(t) = timing.as_ref() { + t.record_count( + "rrdp_rejected_cross_origin_total", + e.is_cross_origin_violation() as u64, + ); + } + return Err(e.into()); + } + }; + drop(_parse_step); + drop(_parse_total); + + let _fetch_step = timing + .as_ref() + .map(|t| t.span_rrdp_repo_step(notification_uri, "fetch_snapshot")); + let _fetch_total = timing + .as_ref() + .map(|t| t.span_phase("rrdp_fetch_snapshot_total")); + let mut dl_span = download_log + .map(|dl| dl.span_download(AuditDownloadKind::RrdpSnapshot, ¬if.snapshot_uri)); + let (snapshot_file, _snapshot_bytes) = match fetch_snapshot_into_tempfile( + fetcher, + ¬if.snapshot_uri, + ¬if.snapshot_hash_sha256, + ¬ification_origin, + ) { + Ok(v) => { + if let Some(t) = timing.as_ref() { + t.record_count("rrdp_snapshot_fetch_ok_total", 1); + t.record_count("rrdp_snapshot_bytes_total", v.1); + } + if let Some(s) = dl_span.as_mut() { + s.set_bytes(v.1); + s.set_ok(); + } + v + } + Err(RrdpSyncError::Fetch(e)) => { + if let Some(t) = timing.as_ref() { + t.record_count("rrdp_snapshot_fetch_fail_total", 1); + } + if let Some(s) = dl_span.as_mut() { + s.set_err(e.clone()); + } + return Err(RrdpSyncError::Fetch(e)); + } + Err(e) => return Err(e), + }; + drop(_fetch_step); + drop(_fetch_total); + + let _apply_step = timing + .as_ref() + .map(|t| t.span_rrdp_repo_step(notification_uri, "apply_snapshot")); + let _apply_total = timing + .as_ref() + .map(|t| t.span_phase("rrdp_apply_snapshot_total")); + let published = apply_snapshot_from_bufread( + store, + notification_uri, + None, + std::io::BufReader::new( + snapshot_file + .reopen() + .map_err(|e| RrdpSyncError::Fetch(format!("tempfile reopen failed: {e}")))?, + ), + notif.session_id, + notif.serial, + )?; + if let Some(t) = timing.as_ref() { + t.record_count("rrdp_snapshot_objects_applied_total", published as u64); + } + drop(_apply_step); + drop(_apply_total); + + let _write_state_step = timing + .as_ref() + .map(|t| t.span_rrdp_repo_step(notification_uri, "write_state")); + let _write_state_total = timing + .as_ref() + .map(|t| t.span_phase("rrdp_write_state_total")); + let state = RrdpState { + session_id: notif.session_id.to_string(), + serial: notif.serial, + }; + persist_rrdp_local_state( + store, + notification_uri, + &state, + RrdpSourceSyncState::SnapshotOnly, + Some(¬if.snapshot_uri), + Some(&hex::encode(notif.snapshot_hash_sha256)), + ) + .map_err(RrdpSyncError::Storage)?; + drop(_write_state_step); + drop(_write_state_total); + + Ok(published) +} diff --git a/src/repository/sync/rrdp/tests.rs b/src/repository/sync/rrdp/tests.rs new file mode 100644 index 0000000..7db3d13 --- /dev/null +++ b/src/repository/sync/rrdp/tests.rs @@ -0,0 +1,5 @@ +// RRDP tests are grouped by parsing, delta application, and synchronization. +include!("tests_parts/parsing.rs"); +include!("tests_parts/delta_apply.rs"); +include!("tests_parts/sync.rs"); +include!("tests_parts/edge_cases.rs"); diff --git a/src/repository/sync/rrdp/tests_parts/delta_apply.rs b/src/repository/sync/rrdp/tests_parts/delta_apply.rs new file mode 100644 index 0000000..1d1afd8 --- /dev/null +++ b/src/repository/sync/rrdp/tests_parts/delta_apply.rs @@ -0,0 +1,359 @@ +// RRDP test group: delta apply. + +#[test] +fn apply_delta_applies_publish_replace_and_withdraw_with_membership_checks() { + let tmp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(tmp.path()).expect("open rocksdb"); + + let sid = "550e8400-e29b-41d4-a716-446655440000"; + let notif_uri = "https://example.net/notification.xml"; + + // Start from snapshot state with a + b + let snapshot_uri = "https://example.net/snapshot.xml"; + let snapshot = snapshot_xml( + sid, + 1, + &[ + ("rsync://example.net/repo/a.mft", b"a1"), + ("rsync://example.net/repo/b.roa", b"b1"), + ], + ); + let snapshot_hash = hex::encode(sha2::Sha256::digest(&snapshot)); + let notif = notification_xml(sid, 1, snapshot_uri, &snapshot_hash); + let fetcher = MapFetcher { + map: HashMap::from([(snapshot_uri.to_string(), snapshot)]), + }; + sync_from_notification_snapshot(&store, notif_uri, ¬if, &fetcher).expect("sync snapshot"); + + let old_b = store + .load_current_object_bytes_by_uri("rsync://example.net/repo/b.roa") + .expect("load current b") + .expect("b present"); + let old_b_hash = hex::encode(sha2::Sha256::digest(old_b.as_slice())); + + let withdraw_a_hash = hex::encode(sha2::Sha256::digest(b"a1".as_slice())); + let publish_c_b64 = base64::engine::general_purpose::STANDARD.encode(b"c2"); + let replace_b_b64 = base64::engine::general_purpose::STANDARD.encode(b"b2"); + + let delta = delta_xml( + sid, + 2, + &[ + &format!( + r#""# + ), + &format!( + r#"{replace_b_b64}"# + ), + &format!(r#"{publish_c_b64}"#), + ], + ); + let delta_hash = sha2::Sha256::digest(&delta); + let mut expected_hash = [0u8; 32]; + expected_hash.copy_from_slice(delta_hash.as_slice()); + + let applied = apply_delta( + &store, + notif_uri, + None, + &delta, + expected_hash, + Uuid::parse_str(sid).unwrap(), + 2, + ) + .expect("apply delta"); + assert_eq!(applied, 3); + + assert_eq!( + store + .load_current_object_bytes_by_uri("rsync://example.net/repo/a.mft") + .expect("load current a"), + None, + "a withdrawn" + ); + let b = store + .load_current_object_bytes_by_uri("rsync://example.net/repo/b.roa") + .expect("load current b") + .expect("b present"); + assert_eq!(b, b"b2"); + let c = store + .load_current_object_bytes_by_uri("rsync://example.net/repo/c.crl") + .expect("load current c") + .expect("c present"); + assert_eq!(c, b"c2"); + + assert!( + !store + .is_current_rrdp_source_member(notif_uri, "rsync://example.net/repo/a.mft") + .expect("is member"), + "a removed from rrdp repo index" + ); + assert!( + store + .is_current_rrdp_source_member(notif_uri, "rsync://example.net/repo/c.crl") + .expect("is member"), + "c added to rrdp repo index" + ); + + let a_view = store + .get_repository_view_entry("rsync://example.net/repo/a.mft") + .expect("get a view") + .expect("a view exists"); + assert_eq!(a_view.state, crate::repository::storage::RepositoryViewState::Withdrawn); + let b_view = store + .get_repository_view_entry("rsync://example.net/repo/b.roa") + .expect("get b view") + .expect("b view exists"); + assert_eq!(b_view.state, crate::repository::storage::RepositoryViewState::Present); + assert_eq!( + b_view.current_hash.as_deref(), + Some(hex::encode(sha2::Sha256::digest(b"b2")).as_str()) + ); + let c_owner = store + .get_rrdp_uri_owner_record("rsync://example.net/repo/c.crl") + .expect("get c owner") + .expect("c owner exists"); + assert_eq!( + c_owner.owner_state, + crate::repository::storage::RrdpUriOwnerState::Active + ); + let a_member = store + .get_rrdp_source_member_record(notif_uri, "rsync://example.net/repo/a.mft") + .expect("get a member") + .expect("a member exists"); + assert!(!a_member.present); + let current_members = store + .list_current_rrdp_source_members(notif_uri) + .expect("list current members"); + assert_eq!( + current_members + .iter() + .map(|record| record.rsync_uri.as_str()) + .collect::>(), + vec![ + "rsync://example.net/repo/b.roa", + "rsync://example.net/repo/c.crl", + ] + ); +} + +#[test] +fn apply_delta_rejects_hash_mismatch() { + let tmp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(tmp.path()).expect("open rocksdb"); + let sid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap(); + let notif_uri = "https://example.net/notification.xml"; + + let delta = delta_xml( + sid.to_string().as_str(), + 1, + &[r#"QQ=="#], + ); + let mut wrong = [0u8; 32]; + wrong[0] = 1; + let err = apply_delta(&store, notif_uri, None, &delta, wrong, sid, 1).unwrap_err(); + assert!(matches!( + err, + RrdpSyncError::Rrdp(RrdpError::DeltaHashMismatch) + )); +} + +#[test] +fn apply_delta_rejects_withdraw_of_non_member() { + let tmp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(tmp.path()).expect("open rocksdb"); + let sid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap(); + let notif_uri = "https://example.net/notification.xml"; + + let withdraw_hash = "00".repeat(32); + let delta = delta_xml( + sid.to_string().as_str(), + 1, + &[&format!( + r#""# + )], + ); + let delta_hash = sha2::Sha256::digest(&delta); + let mut expected_hash = [0u8; 32]; + expected_hash.copy_from_slice(delta_hash.as_slice()); + + let err = apply_delta(&store, notif_uri, None, &delta, expected_hash, sid, 1).unwrap_err(); + assert!(matches!( + err, + RrdpSyncError::Rrdp(RrdpError::DeltaTargetNotFromRepository { .. }) + )); +} + +#[test] +fn apply_delta_rejects_publish_without_hash_for_existing_object() { + let tmp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(tmp.path()).expect("open rocksdb"); + + let sid = "550e8400-e29b-41d4-a716-446655440000"; + let notif_uri = "https://example.net/notification.xml"; + + // Seed snapshot with a.mft. + let snapshot_uri = "https://example.net/snapshot.xml"; + let snapshot = snapshot_xml(sid, 1, &[("rsync://example.net/repo/a.mft", b"a1")]); + let snapshot_hash = hex::encode(sha2::Sha256::digest(&snapshot)); + let notif = notification_xml(sid, 1, snapshot_uri, &snapshot_hash); + let fetcher = MapFetcher { + map: HashMap::from([(snapshot_uri.to_string(), snapshot)]), + }; + sync_from_notification_snapshot(&store, notif_uri, ¬if, &fetcher).expect("seed"); + + // Replace publish for an existing URI must have @hash. + let publish_b64 = base64::engine::general_purpose::STANDARD.encode(b"a2"); + let delta = delta_xml( + sid, + 2, + &[&format!( + r#"{publish_b64}"# + )], + ); + let delta_hash = sha2::Sha256::digest(&delta); + let mut expected_hash = [0u8; 32]; + expected_hash.copy_from_slice(delta_hash.as_slice()); + + let err = apply_delta( + &store, + notif_uri, + None, + &delta, + expected_hash, + Uuid::parse_str(sid).unwrap(), + 2, + ) + .unwrap_err(); + assert!(matches!( + err, + RrdpSyncError::Rrdp(RrdpError::DeltaPublishWithoutHashForExisting { .. }) + )); +} + +#[test] +fn apply_delta_rejects_target_missing_and_hash_mismatch() { + let tmp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(tmp.path()).expect("open rocksdb"); + + let sid = "550e8400-e29b-41d4-a716-446655440000"; + let notif_uri = "https://example.net/notification.xml"; + + // Seed snapshot with a.mft. + let snapshot_uri = "https://example.net/snapshot.xml"; + let snapshot = snapshot_xml(sid, 1, &[("rsync://example.net/repo/a.mft", b"a1")]); + let snapshot_hash = hex::encode(sha2::Sha256::digest(&snapshot)); + let notif = notification_xml(sid, 1, snapshot_uri, &snapshot_hash); + let fetcher = MapFetcher { + map: HashMap::from([(snapshot_uri.to_string(), snapshot)]), + }; + sync_from_notification_snapshot(&store, notif_uri, ¬if, &fetcher).expect("seed"); + + let old_bytes = store + .load_current_object_bytes_by_uri("rsync://example.net/repo/a.mft") + .expect("get") + .expect("present"); + let old_hash = hex::encode(sha2::Sha256::digest(old_bytes.as_slice())); + + // Hash mismatch on withdraw. + let wrong_hash = "11".repeat(32); + let delta = delta_xml( + sid, + 2, + &[&format!( + r#""# + )], + ); + let delta_hash = sha2::Sha256::digest(&delta); + let mut expected_hash = [0u8; 32]; + expected_hash.copy_from_slice(delta_hash.as_slice()); + let err = apply_delta( + &store, + notif_uri, + None, + &delta, + expected_hash, + Uuid::parse_str(sid).unwrap(), + 2, + ) + .unwrap_err(); + assert!(matches!( + err, + RrdpSyncError::Rrdp(RrdpError::DeltaTargetHashMismatch { .. }) + )); + + // Target missing in local cache (index still says it's a member). + store + .delete_repository_view_entry("rsync://example.net/repo/a.mft") + .expect("delete current repository view entry"); + let delta = delta_xml( + sid, + 2, + &[&format!( + r#""# + )], + ); + let delta_hash = sha2::Sha256::digest(&delta); + let mut expected_hash = [0u8; 32]; + expected_hash.copy_from_slice(delta_hash.as_slice()); + let err = apply_delta( + &store, + notif_uri, + None, + &delta, + expected_hash, + Uuid::parse_str(sid).unwrap(), + 2, + ) + .unwrap_err(); + assert!(matches!( + err, + RrdpSyncError::Rrdp(RrdpError::DeltaTargetMissing { .. }) + )); +} + +#[test] +fn apply_delta_rejects_session_and_serial_mismatch() { + let tmp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(tmp.path()).expect("open rocksdb"); + + let sid = "550e8400-e29b-41d4-a716-446655440000"; + let notif_uri = "https://example.net/notification.xml"; + + let publish_b64 = base64::engine::general_purpose::STANDARD.encode(b"x"); + let delta = delta_xml( + sid, + 2, + &[&format!( + r#"{publish_b64}"# + )], + ); + let delta_hash = sha2::Sha256::digest(&delta); + let mut expected_hash = [0u8; 32]; + expected_hash.copy_from_slice(delta_hash.as_slice()); + + // Session mismatch. + let other_sid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440001").unwrap(); + let err = + apply_delta(&store, notif_uri, None, &delta, expected_hash, other_sid, 2).unwrap_err(); + assert!(matches!( + err, + RrdpSyncError::Rrdp(RrdpError::DeltaSessionIdMismatch { .. }) + )); + + // Serial mismatch. + let err = apply_delta( + &store, + notif_uri, + None, + &delta, + expected_hash, + Uuid::parse_str(sid).unwrap(), + 3, + ) + .unwrap_err(); + assert!(matches!( + err, + RrdpSyncError::Rrdp(RrdpError::DeltaSerialMismatch { .. }) + )); +} diff --git a/src/repository/sync/rrdp/tests_parts/edge_cases.rs b/src/repository/sync/rrdp/tests_parts/edge_cases.rs new file mode 100644 index 0000000..2108222 --- /dev/null +++ b/src/repository/sync/rrdp/tests_parts/edge_cases.rs @@ -0,0 +1,214 @@ +// RRDP test group: edge cases. + +#[test] +fn load_rrdp_local_state_uses_source_record_only() { + let tmp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(tmp.path()).expect("open rocksdb"); + let notif_uri = "https://example.net/notification.xml"; + assert_eq!( + load_rrdp_local_state(&store, notif_uri).expect("load empty"), + None + ); + + update_rrdp_source_record_on_success( + &store, + notif_uri, + "source-session", + 9, + crate::repository::storage::RrdpSourceSyncState::DeltaReady, + Some("https://example.net/snapshot.xml"), + Some(&hex::encode([0x11; 32])), + ) + .expect("write source record"); + + let got = load_rrdp_local_state(&store, notif_uri) + .expect("load source preferred") + .expect("source present"); + assert_eq!( + got, + RrdpState { + session_id: "source-session".to_string(), + serial: 9, + } + ); +} + +#[test] +fn sync_from_notification_falls_back_to_snapshot_if_missing_required_deltas() { + let tmp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(tmp.path()).expect("open rocksdb"); + + let sid = "550e8400-e29b-41d4-a716-446655440000"; + let notif_uri = "https://example.net/notification.xml"; + + // Seed state serial=1 with a only. + let snapshot_uri_1 = "https://example.net/snapshot-1.xml"; + let snapshot_1 = snapshot_xml(sid, 1, &[("rsync://example.net/repo/a.mft", b"a1")]); + let snapshot_hash_1 = hex::encode(sha2::Sha256::digest(&snapshot_1)); + let notif_1 = notification_xml(sid, 1, snapshot_uri_1, &snapshot_hash_1); + let fetcher_1 = MapFetcher { + map: HashMap::from([(snapshot_uri_1.to_string(), snapshot_1)]), + }; + sync_from_notification_snapshot(&store, notif_uri, ¬if_1, &fetcher_1).expect("seed"); + + // Notification serial=3 only includes delta serial=3 (contiguous per RFC, but missing + // serial=2 relative to our local state, so we must use snapshot). + let snapshot_uri_3 = "https://example.net/snapshot-3.xml"; + let snapshot_3 = snapshot_xml(sid, 3, &[("rsync://example.net/repo/z.roa", b"z3")]); + let snapshot_hash_3 = hex::encode(sha2::Sha256::digest(&snapshot_3)); + let delta_3_hash_hex = "11".repeat(32); + let notif_3 = notification_xml_with_deltas( + sid, + 3, + snapshot_uri_3, + &snapshot_hash_3, + &[( + "d3", + 3, + "https://example.net/delta-3.xml", + &delta_3_hash_hex, + )], + ); + + let fetcher = MapFetcher { + map: HashMap::from([(snapshot_uri_3.to_string(), snapshot_3)]), + }; + + let published = sync_from_notification(&store, notif_uri, ¬if_3, &fetcher).expect("sync"); + assert_eq!(published, 1); + assert!( + store + .load_current_object_bytes_by_uri("rsync://example.net/repo/z.roa") + .expect("get current") + .is_some() + ); +} + +#[test] +fn sync_from_notification_snapshot_rejects_snapshot_hash_mismatch() { + let tmp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(tmp.path()).expect("open rocksdb"); + + let sid = "550e8400-e29b-41d4-a716-446655440000"; + let serial = 1u64; + let notif_uri = "https://example.net/notification.xml"; + let snapshot_uri = "https://example.net/snapshot.xml"; + + let snapshot = snapshot_xml(sid, serial, &[("rsync://example.net/repo/a.mft", b"x")]); + let notif = notification_xml(sid, serial, snapshot_uri, &"00".repeat(32)); + + let fetcher = MapFetcher { + map: HashMap::from([(snapshot_uri.to_string(), snapshot)]), + }; + let err = sync_from_notification_snapshot(&store, notif_uri, ¬if, &fetcher).unwrap_err(); + assert!(matches!( + err, + RrdpSyncError::Rrdp(RrdpError::SnapshotHashMismatch) + )); +} + +#[test] +fn apply_snapshot_rejects_session_id_and_serial_mismatch() { + let tmp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(tmp.path()).expect("open rocksdb"); + let notif_uri = "https://example.net/notification.xml"; + + let expected_sid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap(); + let got_sid = "550e8400-e29b-41d4-a716-446655440001"; + + let snapshot = snapshot_xml(got_sid, 2, &[("rsync://example.net/repo/a.mft", b"x")]); + let err = apply_snapshot(&store, notif_uri, None, &snapshot, expected_sid, 2).unwrap_err(); + assert!(matches!( + err, + RrdpSyncError::Rrdp(RrdpError::SnapshotSessionIdMismatch { .. }) + )); + + let snapshot = snapshot_xml( + expected_sid.to_string().as_str(), + 3, + &[("rsync://example.net/repo/a.mft", b"x")], + ); + let err = apply_snapshot(&store, notif_uri, None, &snapshot, expected_sid, 2).unwrap_err(); + assert!(matches!( + err, + RrdpSyncError::Rrdp(RrdpError::SnapshotSerialMismatch { .. }) + )); +} + +#[test] +fn strip_all_ascii_whitespace_removes_newlines_and_spaces() { + assert_eq!(strip_all_ascii_whitespace(" a \n b\tc "), "abc"); +} + +#[test] +fn apply_snapshot_reports_publish_errors() { + let tmp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(tmp.path()).expect("open rocksdb"); + let notif_uri = "https://example.net/notification.xml"; + let sid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap(); + + // Missing publish/@uri + let xml = format!( + r#"AA=="# + ) + .into_bytes(); + let err = apply_snapshot(&store, notif_uri, None, &xml, sid, 1).unwrap_err(); + assert!(matches!( + err, + RrdpSyncError::Rrdp(RrdpError::PublishUriMissing) + )); + + // Missing base64 content (no text nodes). + let xml = format!( + r#""# + ) + .into_bytes(); + let err = apply_snapshot(&store, notif_uri, None, &xml, sid, 1).unwrap_err(); + assert!(matches!( + err, + RrdpSyncError::Rrdp(RrdpError::PublishContentMissing) + )); + + // Invalid base64 content. + let xml = format!( + r#"!!!"# + ) + .into_bytes(); + let err = apply_snapshot(&store, notif_uri, None, &xml, sid, 1).unwrap_err(); + assert!(matches!( + err, + RrdpSyncError::Rrdp(RrdpError::PublishBase64(_)) + )); +} + +#[test] +fn apply_snapshot_handles_multiple_publish_batches() { + let tmp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(tmp.path()).expect("open rocksdb"); + let notif_uri = "https://example.net/notification.xml"; + let sid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap(); + + let total = RRDP_SNAPSHOT_APPLY_BATCH_SIZE + 7; + let mut xml = + format!(r#""#); + for i in 0..total { + let uri = format!("rsync://example.net/repo/{i:04}.roa"); + let bytes = format!("payload-{i}").into_bytes(); + let b64 = base64::engine::general_purpose::STANDARD.encode(bytes); + xml.push_str(&format!(r#"{b64}"#)); + } + xml.push_str(""); + + let published = + apply_snapshot(&store, notif_uri, None, xml.as_bytes(), sid, 1).expect("apply snapshot"); + assert_eq!(published, total); + + for idx in [0usize, RRDP_SNAPSHOT_APPLY_BATCH_SIZE - 1, total - 1] { + let uri = format!("rsync://example.net/repo/{idx:04}.roa"); + let got = store + .load_current_object_bytes_by_uri(&uri) + .expect("load object") + .expect("object exists"); + assert_eq!(got, format!("payload-{idx}").into_bytes()); + } +} diff --git a/src/repository/sync/rrdp/tests_parts/parsing.rs b/src/repository/sync/rrdp/tests_parts/parsing.rs new file mode 100644 index 0000000..3ff5e52 --- /dev/null +++ b/src/repository/sync/rrdp/tests_parts/parsing.rs @@ -0,0 +1,374 @@ +// RRDP test group: parsing. + +use super::*; +use crate::output::analysis::timing::{TimingHandle, TimingMeta}; +use crate::repository::current_repo_index::CurrentRepoIndex; +use crate::repository::storage::RocksStore; +use std::collections::HashMap; +use std::io::Read; +use std::time::Duration; + +struct MapFetcher { + map: HashMap>, +} + +impl Fetcher for MapFetcher { + fn fetch(&self, uri: &str) -> Result, String> { + self.map + .get(uri) + .cloned() + .ok_or_else(|| format!("not found: {uri}")) + } +} + +struct SleepyFetcher { + inner: MapFetcher, + sleep_uri: String, + sleep: Duration, +} + +impl Fetcher for SleepyFetcher { + fn fetch(&self, uri: &str) -> Result, String> { + if uri == self.sleep_uri { + std::thread::sleep(self.sleep); + } + self.inner.fetch(uri) + } +} + +struct WriterOnlyFetcher { + map: HashMap>, +} + +impl Fetcher for WriterOnlyFetcher { + fn fetch(&self, uri: &str) -> Result, String> { + Err(format!("unexpected buffered fetch: {uri}")) + } + + fn fetch_to_writer(&self, uri: &str, out: &mut dyn std::io::Write) -> Result { + let bytes = self + .map + .get(uri) + .ok_or_else(|| format!("not found: {uri}"))?; + out.write_all(bytes) + .map_err(|e| format!("write sink failed: {e}"))?; + Ok(bytes.len() as u64) + } +} + +struct NonAsciiWriterFetcher; + +impl Fetcher for NonAsciiWriterFetcher { + fn fetch(&self, uri: &str) -> Result, String> { + Err(format!("unexpected buffered fetch: {uri}")) + } + + fn fetch_to_writer(&self, _uri: &str, out: &mut dyn std::io::Write) -> Result { + out.write_all(&[0x80]) + .map_err(|e| format!("write sink failed: {e}"))?; + Err("snapshot body contains non-ASCII bytes".to_string()) + } +} + +fn assert_current_object(store: &RocksStore, uri: &str, expected: &[u8]) { + assert_eq!( + store + .load_current_object_bytes_by_uri(uri) + .expect("load current object"), + Some(expected.to_vec()) + ); +} + +fn notification_xml( + session_id: &str, + serial: u64, + snapshot_uri: &str, + snapshot_hash: &str, +) -> Vec { + format!( + r#""# + ) + .into_bytes() +} + +fn notification_xml_with_deltas( + session_id: &str, + serial: u64, + snapshot_uri: &str, + snapshot_hash: &str, + deltas: &[(&str, u64, &str, &str)], +) -> Vec { + let mut out = format!( + r#""# + ); + for (_name, delta_serial, uri, hash) in deltas { + out.push_str(&format!( + r#""# + )); + } + out.push_str(""); + out.into_bytes() +} + +fn snapshot_xml(session_id: &str, serial: u64, published: &[(&str, &[u8])]) -> Vec { + let mut out = format!( + r#""# + ); + for (uri, bytes) in published { + let b64 = base64::engine::general_purpose::STANDARD.encode(bytes); + out.push_str(&format!(r#"{b64}"#)); + } + out.push_str(""); + out.into_bytes() +} + +#[test] +fn fetch_snapshot_into_tempfile_streams_and_validates_hash() { + let snapshot_uri = "https://example.test/snapshot.xml"; + let snapshot = b"".to_vec(); + let mut expected_hash = [0u8; 32]; + expected_hash.copy_from_slice(&sha2::Sha256::digest(&snapshot)); + let fetcher = WriterOnlyFetcher { + map: HashMap::from([(snapshot_uri.to_string(), snapshot.clone())]), + }; + let origin = RrdpOrigin::parse(snapshot_uri, RrdpResourceKind::Notification).expect("origin"); + + let (mut file, bytes_written) = + fetch_snapshot_into_tempfile(&fetcher, snapshot_uri, &expected_hash, &origin) + .expect("fetch snapshot into tempfile"); + assert_eq!(bytes_written, snapshot.len() as u64); + let mut got = Vec::new(); + file.as_file_mut() + .read_to_end(&mut got) + .expect("read tempfile"); + assert_eq!(got, snapshot); + + let mut wrong_hash = expected_hash; + wrong_hash[0] ^= 0xff; + let err = + fetch_snapshot_into_tempfile(&fetcher, snapshot_uri, &wrong_hash, &origin).unwrap_err(); + assert!(matches!( + err, + RrdpSyncError::Rrdp(RrdpError::SnapshotHashMismatch) + )); +} + +#[test] +fn fetch_snapshot_into_tempfile_maps_stream_non_ascii_error() { + let err = fetch_snapshot_into_tempfile( + &NonAsciiWriterFetcher, + "https://example.test/snapshot.xml", + &[0u8; 32], + &RrdpOrigin::parse( + "https://example.test/notification.xml", + RrdpResourceKind::Notification, + ) + .expect("origin"), + ) + .unwrap_err(); + assert!(matches!(err, RrdpSyncError::Rrdp(RrdpError::NotAscii))); +} + +#[test] +fn timing_rrdp_repo_step_spans_cover_snapshot_fetch_duration() { + let temp = tempfile::tempdir().expect("tempdir"); + let store_dir = temp.path().join("db"); + let store = RocksStore::open(&store_dir).expect("open rocksdb"); + + let notification_uri = "https://example.test/notification.xml"; + let snapshot_uri = "https://example.test/snapshot.xml"; + let published_uri = "rsync://example.test/repo/a.mft"; + let published_bytes = b"x"; + let session_id = "550e8400-e29b-41d4-a716-446655440000"; + + let snapshot = snapshot_xml(session_id, 1, &[(published_uri, published_bytes)]); + let snapshot_hash = hex::encode(sha2::Sha256::digest(&snapshot)); + let notif = notification_xml(session_id, 1, snapshot_uri, &snapshot_hash); + + let mut map = HashMap::new(); + map.insert(snapshot_uri.to_string(), snapshot); + let fetcher = SleepyFetcher { + inner: MapFetcher { map }, + sleep_uri: snapshot_uri.to_string(), + sleep: Duration::from_millis(25), + }; + + let timing = TimingHandle::new(TimingMeta { + recorded_at_utc_rfc3339: "2026-02-28T00:00:00Z".to_string(), + validation_time_utc_rfc3339: "2026-02-28T00:00:00Z".to_string(), + tal_url: None, + db_path: Some(store_dir.to_string_lossy().into_owned()), + }); + + sync_from_notification_snapshot_with_timing( + &store, + notification_uri, + ¬if, + &fetcher, + Some(&timing), + ) + .expect("rrdp snapshot sync ok"); + + let timing_path = temp.path().join("timing.json"); + timing.write_json(&timing_path, 200).expect("write timing"); + let rep: serde_json::Value = + serde_json::from_slice(&std::fs::read(&timing_path).expect("read timing")) + .expect("parse timing"); + + let want = format!("{notification_uri}::fetch_snapshot"); + let steps = rep + .get("top_rrdp_repo_steps") + .and_then(|v| v.as_array()) + .expect("top_rrdp_repo_steps array"); + let entry = steps + .iter() + .find(|e| e.get("key").and_then(|k| k.as_str()) == Some(want.as_str())) + .unwrap_or_else(|| panic!("missing timing step entry for {want}")); + let nanos = entry + .get("total_nanos") + .and_then(|v| v.as_u64()) + .expect("total_nanos"); + assert!( + nanos >= 20_000_000, + "expected fetch_snapshot timing to include the fetch duration; got {nanos}ns" + ); +} + +#[test] +fn parse_notification_snapshot_rejects_non_ascii() { + let mut xml = b"".to_vec(); + xml.push(0x80); + let err = parse_notification_snapshot(&xml).unwrap_err(); + assert!(matches!(err, RrdpError::NotAscii)); +} + +#[test] +fn parse_notification_snapshot_parses_valid_minimal_notification() { + let sid = "550e8400-e29b-41d4-a716-446655440000"; + let snapshot_uri = "https://example.net/snapshot.xml"; + let hash = "00".repeat(32); + let xml = notification_xml(sid, 7, snapshot_uri, &hash); + let n = parse_notification_snapshot(&xml).expect("parse"); + assert_eq!(n.session_id, Uuid::parse_str(sid).unwrap()); + assert_eq!(n.serial, 7); + assert_eq!(n.snapshot_uri, snapshot_uri); + assert_eq!(hex::encode(n.snapshot_hash_sha256), hash); +} + +#[test] +fn parse_notification_parses_deltas_and_validates_contiguity() { + let sid = "550e8400-e29b-41d4-a716-446655440000"; + let snapshot_uri = "https://example.net/snapshot.xml"; + let hash = "00".repeat(32); + let d_hash_2 = "11".repeat(32); + let d_hash_3 = "22".repeat(32); + // Provide deltas in reverse order to ensure we sort. + let xml = notification_xml_with_deltas( + sid, + 3, + snapshot_uri, + &hash, + &[ + ("d3", 3, "https://example.net/delta-3.xml", &d_hash_3), + ("d2", 2, "https://example.net/delta-2.xml", &d_hash_2), + ], + ); + let n = parse_notification(&xml).expect("parse notification"); + assert_eq!(n.serial, 3); + assert_eq!(n.deltas.len(), 2); + assert_eq!(n.deltas[0].serial, 2); + assert_eq!(n.deltas[1].serial, 3); + assert_eq!(n.deltas[0].uri, "https://example.net/delta-2.xml"); + assert_eq!(hex::encode(n.deltas[1].hash_sha256), d_hash_3); +} + +#[test] +fn parse_notification_rejects_non_contiguous_deltas() { + let sid = "550e8400-e29b-41d4-a716-446655440000"; + let snapshot_uri = "https://example.net/snapshot.xml"; + let hash = "00".repeat(32); + let d_hash_1 = "11".repeat(32); + let d_hash_3 = "22".repeat(32); + // Missing delta serial 2. + let xml = notification_xml_with_deltas( + sid, + 3, + snapshot_uri, + &hash, + &[ + ("d3", 3, "https://example.net/delta-3.xml", &d_hash_3), + ("d1", 1, "https://example.net/delta-1.xml", &d_hash_1), + ], + ); + let err = parse_notification(&xml).unwrap_err(); + assert!(matches!(err, RrdpError::DeltaRefChainNotContiguous { .. })); +} + +fn delta_xml(session_id: &str, serial: u64, elements: &[&str]) -> Vec { + let mut out = format!( + r#""# + ); + for e in elements { + out.push_str(e); + } + out.push_str(""); + out.into_bytes() +} + +#[test] +fn parse_delta_file_parses_publish_and_withdraw() { + let sid = "550e8400-e29b-41d4-a716-446655440000"; + let serial = 3u64; + let publish_bytes = b"abc"; + let publish_b64 = base64::engine::general_purpose::STANDARD.encode(publish_bytes); + let withdraw_hash = "33".repeat(32); + + let xml = delta_xml( + sid, + serial, + &[ + &format!(r#"{publish_b64}"#), + &format!(r#""#), + ], + ); + + let d = parse_delta_file(&xml).expect("parse delta"); + assert_eq!(d.session_id, Uuid::parse_str(sid).unwrap()); + assert_eq!(d.serial, serial); + assert_eq!(d.elements.len(), 2); + match &d.elements[0] { + DeltaElement::Publish { + uri, + hash_sha256, + bytes, + } => { + assert_eq!(uri, "rsync://example.net/repo/a.mft"); + assert_eq!(*hash_sha256, None); + assert_eq!(bytes, publish_bytes); + } + _ => panic!("expected publish"), + } + match &d.elements[1] { + DeltaElement::Withdraw { uri, hash_sha256 } => { + assert_eq!(uri, "rsync://example.net/repo/b.cer"); + assert_eq!(hex::encode(hash_sha256), withdraw_hash); + } + _ => panic!("expected withdraw"), + } +} + +#[test] +fn parse_delta_file_rejects_withdraw_with_content() { + let sid = "550e8400-e29b-41d4-a716-446655440000"; + let serial = 1u64; + let withdraw_hash = "33".repeat(32); + let xml = delta_xml( + sid, + serial, + &[&format!( + r#"AA=="# + )], + ); + let err = parse_delta_file(&xml).unwrap_err(); + assert!(matches!(err, RrdpError::DeltaWithdrawUnexpectedContent)); +} diff --git a/src/repository/sync/rrdp/tests_parts/sync.rs b/src/repository/sync/rrdp/tests_parts/sync.rs new file mode 100644 index 0000000..fd48d79 --- /dev/null +++ b/src/repository/sync/rrdp/tests_parts/sync.rs @@ -0,0 +1,470 @@ +// RRDP test group: sync. + +#[test] +fn sync_from_notification_snapshot_rejects_cross_source_owner_conflict() { + let tmp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(tmp.path()).expect("open rocksdb"); + + let sid_a = "550e8400-e29b-41d4-a716-446655440000"; + let sid_b = "550e8400-e29b-41d4-a716-446655440001"; + let uri = "rsync://example.net/repo/a.mft"; + + let notif_a_uri = "https://example.net/a/notification.xml"; + let snapshot_a_uri = "https://example.net/a/snapshot.xml"; + let snapshot_a = snapshot_xml(sid_a, 1, &[(uri, b"a1")]); + let snapshot_a_hash = hex::encode(sha2::Sha256::digest(&snapshot_a)); + let notif_a = notification_xml(sid_a, 1, snapshot_a_uri, &snapshot_a_hash); + let fetcher_a = MapFetcher { + map: HashMap::from([(snapshot_a_uri.to_string(), snapshot_a)]), + }; + sync_from_notification_snapshot(&store, notif_a_uri, ¬if_a, &fetcher_a) + .expect("seed source a"); + + let notif_b_uri = "https://example.net/b/notification.xml"; + let snapshot_b_uri = "https://example.net/b/snapshot.xml"; + let snapshot_b = snapshot_xml(sid_b, 1, &[(uri, b"b1")]); + let snapshot_b_hash = hex::encode(sha2::Sha256::digest(&snapshot_b)); + let notif_b = notification_xml(sid_b, 1, snapshot_b_uri, &snapshot_b_hash); + let fetcher_b = MapFetcher { + map: HashMap::from([(snapshot_b_uri.to_string(), snapshot_b)]), + }; + + let err = sync_from_notification_snapshot(&store, notif_b_uri, ¬if_b, &fetcher_b) + .expect_err("cross-source overwrite must fail"); + assert!(matches!(err, RrdpSyncError::Storage(_))); + assert!(err.to_string().contains("owner conflict"), "{err}"); +} + +struct PanicOnRrdpFetch; + +impl Fetcher for PanicOnRrdpFetch { + fn fetch(&self, uri: &str) -> Result, String> { + panic!("cross-origin notification must be rejected before fetch: {uri}"); + } +} + +#[test] +fn cross_origin_snapshot_reference_is_rejected_before_fetch_or_state_write() { + let temp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(temp.path()).expect("open rocksdb"); + let notification_uri = "https://origin.example.test/notification.xml"; + let notification = notification_xml( + "550e8400-e29b-41d4-a716-446655440000", + 1, + "https://foreign.example.test/snapshot.xml", + &"00".repeat(32), + ); + + let err = + sync_from_notification_snapshot(&store, notification_uri, ¬ification, &PanicOnRrdpFetch) + .expect_err("cross-origin snapshot must be rejected"); + assert!(matches!( + err, + RrdpSyncError::Rrdp(RrdpError::CrossOriginReference { + resource: RrdpResourceKind::Snapshot, + .. + }) + )); + assert!( + load_rrdp_local_state(&store, notification_uri) + .expect("read state") + .is_none(), + "rejection must precede RRDP state writes" + ); +} + +#[test] +fn cross_origin_delta_reference_is_rejected_even_when_delta_would_not_be_used() { + let temp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(temp.path()).expect("open rocksdb"); + let notification_uri = "https://origin.example.test/notification.xml"; + let notification = notification_xml_with_deltas( + "550e8400-e29b-41d4-a716-446655440000", + 1, + "https://origin.example.test/snapshot.xml", + &"00".repeat(32), + &[( + "delta-1", + 1, + "https://foreign.example.test/delta-1.xml", + &"11".repeat(32), + )], + ); + + let err = sync_from_notification(&store, notification_uri, ¬ification, &PanicOnRrdpFetch) + .expect_err("cross-origin delta must be rejected before state/fetch"); + assert!(matches!( + err, + RrdpSyncError::Rrdp(RrdpError::CrossOriginReference { + resource: RrdpResourceKind::Delta, + .. + }) + )); + assert!( + load_rrdp_local_state(&store, notification_uri) + .expect("read state") + .is_none(), + "all delta references are checked before state reads or writes" + ); +} + +#[test] +fn rrdp_origin_treats_https_default_port_as_equivalent() { + let notification = parse_notification(¬ification_xml( + "550e8400-e29b-41d4-a716-446655440000", + 1, + "https://origin.example.test:443/snapshot.xml", + &"00".repeat(32), + )) + .expect("notification"); + validate_notification_references( + "https://origin.example.test/notification.xml", + ¬ification, + ) + .expect("explicit HTTPS default port must remain same-origin"); +} + +#[test] +fn sync_from_notification_snapshot_applies_snapshot_and_stores_state() { + let tmp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(tmp.path()).expect("open rocksdb"); + + let sid = "550e8400-e29b-41d4-a716-446655440000"; + let serial = 9u64; + let notif_uri = "https://example.net/notification.xml"; + let snapshot_uri = "https://example.net/snapshot.xml"; + + let snapshot = snapshot_xml( + sid, + serial, + &[ + ("rsync://example.net/repo/a.mft", b"mft-bytes"), + ("rsync://example.net/repo/b.roa", b"roa-bytes"), + ], + ); + let snapshot_hash = hex::encode(sha2::Sha256::digest(&snapshot)); + let notif = notification_xml(sid, serial, snapshot_uri, &snapshot_hash); + + let fetcher = MapFetcher { + map: HashMap::from([(snapshot_uri.to_string(), snapshot.clone())]), + }; + + let published = + sync_from_notification_snapshot(&store, notif_uri, ¬if, &fetcher).expect("sync"); + assert_eq!(published, 2); + + assert_current_object(&store, "rsync://example.net/repo/a.mft", b"mft-bytes"); + assert_current_object(&store, "rsync://example.net/repo/b.roa", b"roa-bytes"); + + let state = load_rrdp_local_state(&store, notif_uri) + .expect("get rrdp state") + .expect("state present"); + assert_eq!(state.session_id, sid); + assert_eq!(state.serial, serial); + + let source = store + .get_rrdp_source_record(notif_uri) + .expect("get rrdp source") + .expect("rrdp source exists"); + assert_eq!(source.last_session_id.as_deref(), Some(sid)); + assert_eq!(source.last_serial, Some(serial)); + assert_eq!( + source.sync_state, + crate::repository::storage::RrdpSourceSyncState::SnapshotOnly + ); + + let view = store + .get_repository_view_entry("rsync://example.net/repo/a.mft") + .expect("get repository view") + .expect("repository view exists"); + assert_eq!(view.state, crate::repository::storage::RepositoryViewState::Present); + assert_eq!(view.repository_source.as_deref(), Some(notif_uri)); + + let current_bytes = store + .load_current_object_bytes_by_uri("rsync://example.net/repo/a.mft") + .expect("load current bytes") + .expect("current object bytes exist"); + assert_eq!(current_bytes, b"mft-bytes".to_vec()); + assert!( + store + .get_raw_by_hash_entry(hex::encode(sha2::Sha256::digest(b"mft-bytes")).as_str()) + .expect("get raw_by_hash") + .is_none() + ); + + let member = store + .get_rrdp_source_member_record(notif_uri, "rsync://example.net/repo/a.mft") + .expect("get member") + .expect("member exists"); + assert!(member.present); + let owner = store + .get_rrdp_uri_owner_record("rsync://example.net/repo/a.mft") + .expect("get owner") + .expect("owner exists"); + assert_eq!(owner.notify_uri, notif_uri); + assert_eq!(owner.owner_state, crate::repository::storage::RrdpUriOwnerState::Active); +} + +#[test] +fn sync_from_notification_snapshot_deletes_objects_not_in_new_snapshot() { + let tmp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(tmp.path()).expect("open rocksdb"); + + let sid = "550e8400-e29b-41d4-a716-446655440000"; + let notif_uri = "https://example.net/notification.xml"; + + // serial 1: publish a + b + let snapshot_uri_1 = "https://example.net/snapshot-1.xml"; + let snapshot_1 = snapshot_xml( + sid, + 1, + &[ + ("rsync://example.net/repo/a.mft", b"a1"), + ("rsync://example.net/repo/b.roa", b"b1"), + ], + ); + let snapshot_hash_1 = hex::encode(sha2::Sha256::digest(&snapshot_1)); + let notif_1 = notification_xml(sid, 1, snapshot_uri_1, &snapshot_hash_1); + + let fetcher_1 = MapFetcher { + map: HashMap::from([(snapshot_uri_1.to_string(), snapshot_1)]), + }; + sync_from_notification_snapshot(&store, notif_uri, ¬if_1, &fetcher_1).expect("sync 1"); + + // serial 2: publish b (new bytes) + c, and drop a + let snapshot_uri_2 = "https://example.net/snapshot-2.xml"; + let snapshot_2 = snapshot_xml( + sid, + 2, + &[ + ("rsync://example.net/repo/b.roa", b"b2"), + ("rsync://example.net/repo/c.crl", b"c2"), + ], + ); + let snapshot_hash_2 = hex::encode(sha2::Sha256::digest(&snapshot_2)); + let notif_2 = notification_xml(sid, 2, snapshot_uri_2, &snapshot_hash_2); + + let fetcher_2 = MapFetcher { + map: HashMap::from([(snapshot_uri_2.to_string(), snapshot_2)]), + }; + sync_from_notification_snapshot(&store, notif_uri, ¬if_2, &fetcher_2).expect("sync 2"); + + assert!( + store + .load_current_object_bytes_by_uri("rsync://example.net/repo/a.mft") + .expect("get current a") + .is_none(), + "a should be deleted by full-state snapshot apply" + ); + + assert_current_object(&store, "rsync://example.net/repo/b.roa", b"b2"); + assert_current_object(&store, "rsync://example.net/repo/c.crl", b"c2"); +} + +#[test] +fn sync_from_notification_uses_deltas_when_available_for_local_state() { + let tmp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(tmp.path()).expect("open rocksdb"); + + let sid = "550e8400-e29b-41d4-a716-446655440000"; + let notif_uri = "https://example.net/notification.xml"; + + // Seed state with snapshot serial=1 containing a+b. + let snapshot_uri_1 = "https://example.net/snapshot-1.xml"; + let snapshot_1 = snapshot_xml( + sid, + 1, + &[ + ("rsync://example.net/repo/a.mft", b"a1"), + ("rsync://example.net/repo/b.roa", b"b1"), + ], + ); + let snapshot_hash_1 = hex::encode(sha2::Sha256::digest(&snapshot_1)); + let notif_1 = notification_xml(sid, 1, snapshot_uri_1, &snapshot_hash_1); + let fetcher_1 = MapFetcher { + map: HashMap::from([(snapshot_uri_1.to_string(), snapshot_1)]), + }; + sync_from_notification_snapshot(&store, notif_uri, ¬if_1, &fetcher_1).expect("seed"); + + // Notification serial=3 with deltas 2 and 3. Snapshot URI is intentionally not fetchable + // to assert we really use deltas. + let snapshot_uri_3 = "https://example.net/snapshot-3.xml"; + let snapshot_hash_3 = "00".repeat(32); + + let publish_c_b64 = base64::engine::general_purpose::STANDARD.encode(b"c2"); + let delta_2 = delta_xml( + sid, + 2, + &[&format!( + r#"{publish_c_b64}"# + )], + ); + let delta_2_hash_hex = hex::encode(sha2::Sha256::digest(&delta_2)); + + let c_hash_hex = hex::encode(sha2::Sha256::digest(b"c2".as_slice())); + let delta_3 = delta_xml( + sid, + 3, + &[&format!( + r#""# + )], + ); + let delta_3_hash_hex = hex::encode(sha2::Sha256::digest(&delta_3)); + + let notif_3 = notification_xml_with_deltas( + sid, + 3, + snapshot_uri_3, + &snapshot_hash_3, + &[ + ( + "d3", + 3, + "https://example.net/delta-3.xml", + &delta_3_hash_hex, + ), + ( + "d2", + 2, + "https://example.net/delta-2.xml", + &delta_2_hash_hex, + ), + ], + ); + + let fetcher = MapFetcher { + map: HashMap::from([ + ("https://example.net/delta-2.xml".to_string(), delta_2), + ("https://example.net/delta-3.xml".to_string(), delta_3), + ]), + }; + + let applied = sync_from_notification(&store, notif_uri, ¬if_3, &fetcher).expect("sync"); + assert!(applied > 0); + + // Delta 2 publishes c then delta 3 withdraws it => final state should not contain c. + assert!( + store + .load_current_object_bytes_by_uri("rsync://example.net/repo/c.crl") + .expect("get current") + .is_none() + ); + + let state = load_rrdp_local_state(&store, notif_uri) + .expect("get rrdp state") + .expect("state present"); + assert_eq!(state.session_id, Uuid::parse_str(sid).unwrap().to_string()); + assert_eq!(state.serial, 3); +} + +#[test] +fn sync_from_notification_same_serial_hydrates_current_repo_index() { + let tmp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(tmp.path()).expect("open rocksdb"); + + let sid = "550e8400-e29b-41d4-a716-446655440000"; + let notif_uri = "https://example.net/notification.xml"; + let snapshot_uri = "https://example.net/snapshot.xml"; + let uri_a = "rsync://example.net/repo/a.mft"; + let uri_b = "rsync://example.net/repo/b.roa"; + + let snapshot = snapshot_xml(sid, 1, &[(uri_a, b"a1"), (uri_b, b"b1")]); + let snapshot_hash = hex::encode(sha2::Sha256::digest(&snapshot)); + let notif = notification_xml(sid, 1, snapshot_uri, &snapshot_hash); + let fetcher_1 = MapFetcher { + map: HashMap::from([(snapshot_uri.to_string(), snapshot)]), + }; + sync_from_notification_snapshot(&store, notif_uri, ¬if, &fetcher_1).expect("seed"); + + let index = CurrentRepoIndex::shared(); + let no_fetcher = MapFetcher { + map: HashMap::new(), + }; + let applied = sync_from_notification_with_timing_and_download_log( + &store, + notif_uri, + Some(&index), + ¬if, + &no_fetcher, + None, + None, + ) + .expect("same serial no-op"); + assert_eq!(applied, 0); + + let index = index.read().expect("read-lock index"); + assert_eq!(index.active_uri_count(), 2); + assert!(index.get_by_uri(uri_a).is_some()); + assert!(index.get_by_uri(uri_b).is_some()); +} + +#[test] +fn sync_from_notification_delta_hydrates_unchanged_current_repo_entries() { + let tmp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(tmp.path()).expect("open rocksdb"); + + let sid = "550e8400-e29b-41d4-a716-446655440000"; + let notif_uri = "https://example.net/notification.xml"; + let snapshot_uri_1 = "https://example.net/snapshot-1.xml"; + let uri_a = "rsync://example.net/repo/a.mft"; + let uri_b = "rsync://example.net/repo/b.roa"; + let uri_c = "rsync://example.net/repo/c.crl"; + + let snapshot_1 = snapshot_xml(sid, 1, &[(uri_a, b"a1"), (uri_b, b"b1")]); + let snapshot_hash_1 = hex::encode(sha2::Sha256::digest(&snapshot_1)); + let notif_1 = notification_xml(sid, 1, snapshot_uri_1, &snapshot_hash_1); + let fetcher_1 = MapFetcher { + map: HashMap::from([(snapshot_uri_1.to_string(), snapshot_1)]), + }; + sync_from_notification_snapshot(&store, notif_uri, ¬if_1, &fetcher_1).expect("seed"); + + let publish_c_b64 = base64::engine::general_purpose::STANDARD.encode(b"c2"); + let delta_2 = delta_xml( + sid, + 2, + &[&format!( + r#"{publish_c_b64}"# + )], + ); + let delta_2_hash_hex = hex::encode(sha2::Sha256::digest(&delta_2)); + let notif_2 = notification_xml_with_deltas( + sid, + 2, + "https://example.net/snapshot-2.xml", + &"00".repeat(32), + &[( + "d2", + 2, + "https://example.net/delta-2.xml", + &delta_2_hash_hex, + )], + ); + let fetcher_2 = MapFetcher { + map: HashMap::from([("https://example.net/delta-2.xml".to_string(), delta_2)]), + }; + + let index = CurrentRepoIndex::shared(); + let applied = sync_from_notification_with_timing_and_download_log( + &store, + notif_uri, + Some(&index), + ¬if_2, + &fetcher_2, + None, + None, + ) + .expect("delta sync"); + assert_eq!(applied, 1); + + let index = index.read().expect("read-lock index"); + assert_eq!(index.active_uri_count(), 3); + assert!( + index.get_by_uri(uri_a).is_some(), + "unchanged object from the previous serial must be visible" + ); + assert!( + index.get_by_uri(uri_b).is_some(), + "unchanged object from the previous serial must be visible" + ); + assert!(index.get_by_uri(uri_c).is_some(), "delta publish visible"); +} diff --git a/src/repository/sync/store_projection.rs b/src/repository/sync/store_projection.rs new file mode 100644 index 0000000..312c949 --- /dev/null +++ b/src/repository/sync/store_projection.rs @@ -0,0 +1,510 @@ +use std::collections::BTreeMap; + +use crate::repository::storage::{ + PackTime, RawByHashEntry, RepositoryViewEntry, RepositoryViewState, RocksStore, + RrdpSourceMemberRecord, RrdpSourceRecord, RrdpSourceSyncState, RrdpUriOwnerRecord, + RrdpUriOwnerState, +}; +use sha2::Digest; + +#[allow(dead_code)] +pub struct PreparedRawByHashBatch { + pub uri_to_hash: BTreeMap, + pub entries_to_write: Vec, +} + +pub struct PreparedRepoBytesBatch { + pub uri_to_hash: BTreeMap, + pub blobs_to_write: Vec<(String, Vec)>, +} + +pub fn prepare_repo_bytes_batch( + objects: &[(String, Vec)], +) -> Result { + prepare_repo_bytes_batch_owned(objects.to_vec()) +} + +pub fn prepare_repo_bytes_batch_owned( + objects: Vec<(String, Vec)>, +) -> Result { + let mut uri_to_hash: BTreeMap = BTreeMap::new(); + let mut pending: BTreeMap> = BTreeMap::new(); + + for (uri, bytes) in objects { + if bytes.is_empty() { + return Err(format!("repo bytes for {uri} must not be empty")); + } + let sha256_hex = compute_sha256_hex(&bytes); + uri_to_hash.insert(uri.clone(), sha256_hex.clone()); + match pending.entry(sha256_hex) { + std::collections::btree_map::Entry::Vacant(slot) => { + slot.insert(bytes); + } + std::collections::btree_map::Entry::Occupied(existing) => { + if existing.get() != &bytes { + return Err(format!( + "repo bytes collision for {uri}: same sha256 maps to different bytes" + )); + } + } + } + } + + Ok(PreparedRepoBytesBatch { + uri_to_hash, + blobs_to_write: pending.into_iter().collect(), + }) +} + +#[allow(dead_code)] +pub fn prepare_raw_by_hash_evidence_batch( + store: &RocksStore, + objects: &[(String, Vec)], +) -> Result { + let mut pending: BTreeMap = BTreeMap::new(); + let mut uri_to_hash: BTreeMap = BTreeMap::new(); + + for (uri, bytes) in objects { + let sha256_hex = compute_sha256_hex(bytes); + uri_to_hash.insert(uri.clone(), sha256_hex.clone()); + let entry = pending + .entry(sha256_hex.clone()) + .or_insert_with(|| RawByHashEntry::from_bytes(sha256_hex.clone(), bytes.clone())); + + if entry.bytes != *bytes { + return Err(format!( + "raw_by_hash collision for {uri}: same sha256 maps to different bytes" + )); + } + if !entry.origin_uris.iter().any(|existing| existing == uri) { + entry.origin_uris.push(uri.clone()); + } + if entry.object_type.is_none() { + entry.object_type = infer_object_type_from_uri(uri); + } + } + + let hashes: Vec = pending.keys().cloned().collect(); + let existing_entries = store + .get_raw_by_hash_entries_batch(&hashes) + .map_err(|e| e.to_string())?; + + let mut entries_to_write = Vec::new(); + for (hash, existing_opt) in hashes.into_iter().zip(existing_entries.into_iter()) { + let mut pending_entry = pending.remove(&hash).expect("pending raw_by_hash entry"); + match existing_opt { + Some(mut existing) => { + if existing.bytes != pending_entry.bytes { + return Err(format!( + "raw_by_hash collision for hash {hash}: same sha256 maps to different bytes" + )); + } + let mut changed = false; + for uri in pending_entry.origin_uris.drain(..) { + if !existing + .origin_uris + .iter() + .any(|existing_uri| existing_uri == &uri) + { + existing.origin_uris.push(uri); + changed = true; + } + } + if existing.object_type.is_none() && pending_entry.object_type.is_some() { + existing.object_type = pending_entry.object_type; + changed = true; + } + if changed { + entries_to_write.push(existing); + } + } + None => entries_to_write.push(pending_entry), + } + } + + Ok(PreparedRawByHashBatch { + uri_to_hash, + entries_to_write, + }) +} + +pub fn upsert_repo_blob_bytes(store: &RocksStore, bytes: &[u8]) -> Result { + if bytes.is_empty() { + return Err("repo bytes must not be empty".to_string()); + } + let sha256_hex = compute_sha256_hex(bytes); + store + .put_blob_bytes_batch(&[(sha256_hex.clone(), bytes.to_vec())]) + .map_err(|e| e.to_string())?; + Ok(sha256_hex) +} + +pub fn infer_object_type_from_uri(uri: &str) -> Option { + let ext = uri.rsplit('.').next()?; + let ext = ext.to_ascii_lowercase(); + match ext.as_str() { + "cer" | "crl" | "mft" | "roa" | "asa" | "gbr" | "tal" | "xml" => Some(ext), + _ => None, + } +} + +pub fn build_repository_view_present_entry( + repository_source: &str, + rsync_uri: &str, + current_hash: &str, +) -> RepositoryViewEntry { + RepositoryViewEntry { + rsync_uri: rsync_uri.to_string(), + current_hash: Some(current_hash.to_string()), + repository_source: Some(repository_source.to_string()), + object_type: infer_object_type_from_uri(rsync_uri), + state: RepositoryViewState::Present, + } +} + +pub fn build_repository_view_withdrawn_entry( + repository_source: &str, + rsync_uri: &str, + current_hash: Option, +) -> RepositoryViewEntry { + RepositoryViewEntry { + rsync_uri: rsync_uri.to_string(), + current_hash, + repository_source: Some(repository_source.to_string()), + object_type: infer_object_type_from_uri(rsync_uri), + state: RepositoryViewState::Withdrawn, + } +} + +pub fn build_rrdp_source_member_present_record( + notification_uri: &str, + session_id: &str, + serial: u64, + rsync_uri: &str, + current_hash: &str, +) -> RrdpSourceMemberRecord { + RrdpSourceMemberRecord { + notify_uri: notification_uri.to_string(), + rsync_uri: rsync_uri.to_string(), + current_hash: Some(current_hash.to_string()), + object_type: infer_object_type_from_uri(rsync_uri), + present: true, + last_confirmed_session_id: session_id.to_string(), + last_confirmed_serial: serial, + last_changed_at: now_pack_time(), + } +} + +pub fn build_rrdp_source_member_withdrawn_record( + notification_uri: &str, + session_id: &str, + serial: u64, + rsync_uri: &str, + current_hash: Option, +) -> RrdpSourceMemberRecord { + RrdpSourceMemberRecord { + notify_uri: notification_uri.to_string(), + rsync_uri: rsync_uri.to_string(), + current_hash, + object_type: infer_object_type_from_uri(rsync_uri), + present: false, + last_confirmed_session_id: session_id.to_string(), + last_confirmed_serial: serial, + last_changed_at: now_pack_time(), + } +} + +pub fn build_rrdp_uri_owner_active_record( + notification_uri: &str, + session_id: &str, + serial: u64, + rsync_uri: &str, + current_hash: &str, +) -> RrdpUriOwnerRecord { + RrdpUriOwnerRecord { + rsync_uri: rsync_uri.to_string(), + notify_uri: notification_uri.to_string(), + current_hash: Some(current_hash.to_string()), + last_confirmed_session_id: session_id.to_string(), + last_confirmed_serial: serial, + last_changed_at: now_pack_time(), + owner_state: RrdpUriOwnerState::Active, + } +} + +pub fn build_rrdp_uri_owner_withdrawn_record( + notification_uri: &str, + session_id: &str, + serial: u64, + rsync_uri: &str, + current_hash: Option, +) -> RrdpUriOwnerRecord { + RrdpUriOwnerRecord { + rsync_uri: rsync_uri.to_string(), + notify_uri: notification_uri.to_string(), + current_hash, + last_confirmed_session_id: session_id.to_string(), + last_confirmed_serial: serial, + last_changed_at: now_pack_time(), + owner_state: RrdpUriOwnerState::Withdrawn, + } +} + +#[allow(dead_code)] +pub fn upsert_raw_by_hash_evidence( + store: &RocksStore, + rsync_uri: &str, + bytes: &[u8], +) -> Result { + let prepared = + prepare_raw_by_hash_evidence_batch(store, &[(rsync_uri.to_string(), bytes.to_vec())])?; + store + .put_raw_by_hash_entries_batch_unchecked(&prepared.entries_to_write) + .map_err(|e| e.to_string())?; + let sha256_hex = prepared + .uri_to_hash + .get(rsync_uri) + .cloned() + .expect("raw_by_hash mapping for upsert input"); + Ok(sha256_hex) +} + +pub fn put_repository_view_present( + store: &RocksStore, + repository_source: &str, + rsync_uri: &str, + current_hash: &str, +) -> Result<(), String> { + let entry = build_repository_view_present_entry(repository_source, rsync_uri, current_hash); + store + .put_repository_view_entry(&entry) + .map_err(|e| e.to_string()) +} + +pub fn put_repository_view_withdrawn( + store: &RocksStore, + repository_source: &str, + rsync_uri: &str, + current_hash: Option, +) -> Result<(), String> { + let entry = build_repository_view_withdrawn_entry(repository_source, rsync_uri, current_hash); + store + .put_repository_view_entry(&entry) + .map_err(|e| e.to_string()) +} + +pub fn ensure_rrdp_uri_can_be_owned_by( + store: &RocksStore, + notification_uri: &str, + rsync_uri: &str, +) -> Result<(), String> { + let Some(owner) = store + .get_rrdp_uri_owner_record(rsync_uri) + .map_err(|e| e.to_string())? + else { + return Ok(()); + }; + + if owner.notify_uri != notification_uri && owner.owner_state == RrdpUriOwnerState::Active { + return Err(format!( + "RRDP source owner conflict for {rsync_uri}: current owner {} but incoming source {}", + owner.notify_uri, notification_uri + )); + } + Ok(()) +} + +pub fn current_rrdp_owner_is( + store: &RocksStore, + notification_uri: &str, + rsync_uri: &str, +) -> Result { + Ok(matches!( + store + .get_rrdp_uri_owner_record(rsync_uri) + .map_err(|e| e.to_string())?, + Some(owner) + if owner.notify_uri == notification_uri && owner.owner_state == RrdpUriOwnerState::Active + )) +} + +pub fn put_rrdp_source_member_present( + store: &RocksStore, + notification_uri: &str, + session_id: &str, + serial: u64, + rsync_uri: &str, + current_hash: &str, +) -> Result<(), String> { + let record = build_rrdp_source_member_present_record( + notification_uri, + session_id, + serial, + rsync_uri, + current_hash, + ); + store + .put_rrdp_source_member_record(&record) + .map_err(|e| e.to_string()) +} + +pub fn put_rrdp_source_member_withdrawn( + store: &RocksStore, + notification_uri: &str, + session_id: &str, + serial: u64, + rsync_uri: &str, + current_hash: Option, +) -> Result<(), String> { + let record = build_rrdp_source_member_withdrawn_record( + notification_uri, + session_id, + serial, + rsync_uri, + current_hash, + ); + store + .put_rrdp_source_member_record(&record) + .map_err(|e| e.to_string()) +} + +pub fn put_rrdp_uri_owner_active( + store: &RocksStore, + notification_uri: &str, + session_id: &str, + serial: u64, + rsync_uri: &str, + current_hash: &str, +) -> Result<(), String> { + let record = build_rrdp_uri_owner_active_record( + notification_uri, + session_id, + serial, + rsync_uri, + current_hash, + ); + store + .put_rrdp_uri_owner_record(&record) + .map_err(|e| e.to_string()) +} + +pub fn put_rrdp_uri_owner_withdrawn( + store: &RocksStore, + notification_uri: &str, + session_id: &str, + serial: u64, + rsync_uri: &str, + current_hash: Option, +) -> Result<(), String> { + let record = build_rrdp_uri_owner_withdrawn_record( + notification_uri, + session_id, + serial, + rsync_uri, + current_hash, + ); + store + .put_rrdp_uri_owner_record(&record) + .map_err(|e| e.to_string()) +} + +pub fn update_rrdp_source_record_on_success( + store: &RocksStore, + notification_uri: &str, + session_id: &str, + serial: u64, + sync_state: RrdpSourceSyncState, + last_snapshot_uri: Option<&str>, + last_snapshot_hash_hex: Option<&str>, +) -> Result<(), String> { + let now = now_pack_time(); + let mut record = match store + .get_rrdp_source_record(notification_uri) + .map_err(|e| e.to_string())? + { + Some(existing) => existing, + None => RrdpSourceRecord { + notify_uri: notification_uri.to_string(), + last_session_id: None, + last_serial: None, + first_seen_at: now.clone(), + last_seen_at: now.clone(), + last_sync_at: None, + sync_state, + last_snapshot_uri: None, + last_snapshot_hash: None, + last_error: None, + }, + }; + + record.last_session_id = Some(session_id.to_string()); + record.last_serial = Some(serial); + record.last_seen_at = now.clone(); + record.last_sync_at = Some(now); + record.sync_state = sync_state; + record.last_snapshot_uri = last_snapshot_uri.map(str::to_string); + record.last_snapshot_hash = last_snapshot_hash_hex.map(str::to_string); + record.last_error = None; + + store + .put_rrdp_source_record(&record) + .map_err(|e| e.to_string()) +} + +pub fn compute_sha256_hex(bytes: &[u8]) -> String { + hex::encode(sha2::Sha256::digest(bytes)) +} + +pub fn now_pack_time() -> PackTime { + PackTime::from_utc_offset_datetime(time::OffsetDateTime::now_utc()) +} + +#[cfg(test)] +mod tests { + use super::{prepare_repo_bytes_batch, prepare_repo_bytes_batch_owned}; + use std::collections::BTreeSet; + + #[test] + fn prepare_repo_bytes_batch_deduplicates_by_hash() { + let objects = vec![ + ( + "rsync://example.test/repo/a.roa".to_string(), + b"same".to_vec(), + ), + ( + "rsync://example.test/repo/b.roa".to_string(), + b"same".to_vec(), + ), + ( + "rsync://example.test/repo/c.roa".to_string(), + b"other".to_vec(), + ), + ]; + let prepared = prepare_repo_bytes_batch(&objects).expect("prepare repo bytes"); + assert_eq!(prepared.uri_to_hash.len(), 3); + assert_eq!(prepared.blobs_to_write.len(), 2); + let unique_hashes = prepared + .blobs_to_write + .iter() + .map(|(hash, _)| hash.clone()) + .collect::>(); + assert_eq!(unique_hashes.len(), 2); + } + + #[test] + fn prepare_repo_bytes_batch_owned_deduplicates_without_borrowed_input() { + let objects = vec![ + ( + "rsync://example.test/repo/a.roa".to_string(), + b"same".to_vec(), + ), + ( + "rsync://example.test/repo/b.roa".to_string(), + b"same".to_vec(), + ), + ]; + let prepared = prepare_repo_bytes_batch_owned(objects).expect("prepare repo bytes"); + assert_eq!(prepared.uri_to_hash.len(), 2); + assert_eq!(prepared.blobs_to_write.len(), 1); + } +} diff --git a/src/runtime.rs b/src/runtime.rs new file mode 100644 index 0000000..39b93df --- /dev/null +++ b/src/runtime.rs @@ -0,0 +1,43 @@ +#![allow(clippy::too_many_arguments)] + +mod output; +mod report_tasks; + +use crate::ccr::CcrAccumulator; +use std::path::{Path, PathBuf}; + +use crate::output::analysis::timing::{ + DurationStats, TimingHandle, TimingMeta, TimingMetaUpdate, TopDurationEntry, +}; +use crate::output::memory::{ + MallocTrimProbe, MemoryTelemetryCheckpoint, MemoryTelemetrySummary, ObjectGraphMemoryMetric, + ObjectGraphMemorySection, ObjectGraphMemorySummary, +}; +use crate::repository::fetch::http::{BlockingHttpFetcher, HttpFetcherConfig}; +use crate::repository::fetch::rsync::LocalDirRsyncFetcher; +use crate::repository::fetch::rsync_system::{ + RsyncScopePolicy, SystemRsyncConfig, SystemRsyncFetcher, +}; +use crate::repository::storage::RocksStore; +use crate::scheduler::config::{ParallelPhase1Config, ParallelPhase2Config}; +use crate::scheduler::types::TalInputSpec; +use crate::ta_constraints::TaConstraintsByTal; +use crate::validation::policy::{Policy, ResourceValidationMode, StrictPolicy}; +use crate::validation::run_tree_from_tal::{ + RunTreeFromTalAuditOutput, run_tree_from_multiple_tals_parallel_phase2_audit, + run_tree_from_multiple_tals_parallel_phase2_audit_with_timing, +}; +use crate::validation::tree::TreeRunConfig; +use output::{ReportJsonFormat, run_compare_view_task, write_stage_timing}; +use report_tasks::{ReportTaskOutput, build_repo_sync_stats, run_ccr_task, run_report_task}; +use serde::Serialize; +use std::collections::HashMap; +use std::sync::Arc; + +include!("runtime/types.rs"); +include!("runtime/post_validation.rs"); +include!("runtime/report.rs"); +include!("runtime/run.rs"); + +#[cfg(test)] +mod tests; diff --git a/src/runtime/output.rs b/src/runtime/output.rs new file mode 100644 index 0000000..556691e --- /dev/null +++ b/src/runtime/output.rs @@ -0,0 +1,419 @@ +use std::io::BufWriter; +use std::path::Path; + +use serde::Serialize; +use serde::ser::SerializeSeq; +use sha2::Digest; + +use crate::ccr::canonical_vrp_prefix; +use crate::output::audit::{ + AspaOutput, AuditRunMeta, AuditWarning, QueryAuditManifest, ValidationEvent, + ValidationEventCounts, VrpOutput, +}; + +use super::{PostValidationShared, RunStageTiming}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(super) enum ReportJsonFormat { + Pretty, + Compact, +} + +pub(super) fn write_json( + path: &Path, + report: &T, + format: ReportJsonFormat, +) -> Result<(), String> { + let f = std::fs::File::create(path) + .map_err(|e| format!("create report file failed: {}: {e}", path.display()))?; + let writer = BufWriter::new(f); + match format { + ReportJsonFormat::Pretty => serde_json::to_writer_pretty(writer, report), + ReportJsonFormat::Compact => serde_json::to_writer(writer, report), + } + .map_err(|e| format!("write report json failed: {e}"))?; + Ok(()) +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(super) struct ReportJsonWriteTiming { + pub(super) build_ms: u64, + pub(super) write_ms: u64, +} + +#[derive(Serialize)] +struct BorrowedAuditReportV2<'a> { + format_version: u32, + meta: AuditRunMeta, + policy: &'a crate::validation::policy::Policy, + tree: BorrowedTreeSummary<'a>, + publication_points: &'a [crate::output::audit::PublicationPointAudit], + vrps: VrpReportSequence<'a>, + aspas: AspaReportSequence<'a>, + downloads: &'a [crate::output::audit::AuditDownloadEvent], + download_stats: &'a crate::output::audit::AuditDownloadStats, + repo_sync_stats: crate::output::audit::AuditRepoSyncStats, + #[serde(rename = "queryAudit", skip_serializing_if = "Option::is_none")] + query_audit: Option, +} + +#[derive(Serialize)] +struct BorrowedTreeSummary<'a> { + instances_processed: usize, + instances_failed: usize, + warnings: WarningReportSequence<'a>, +} + +struct WarningReportSequence<'a>(&'a [crate::output::report::Warning]); + +impl Serialize for WarningReportSequence<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.0.len()))?; + for warning in self.0 { + seq.serialize_element(&AuditWarning::from(warning))?; + } + seq.end() + } +} + +struct VrpReportSequence<'a>(&'a [crate::validation::objects::Vrp]); + +impl Serialize for VrpReportSequence<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.0.len()))?; + for vrp in self.0 { + seq.serialize_element(&VrpOutput { + asn: vrp.asn, + prefix: crate::output::audit::format_roa_ip_prefix(&vrp.prefix), + max_length: vrp.max_length, + })?; + } + seq.end() + } +} + +struct AspaReportSequence<'a>(&'a [crate::validation::objects::AspaAttestation]); + +impl Serialize for AspaReportSequence<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.0.len()))?; + for aspa in self.0 { + seq.serialize_element(&AspaOutput { + customer_as_id: aspa.customer_as_id, + provider_as_ids: aspa.provider_as_ids.clone(), + })?; + } + seq.end() + } +} + +pub(super) fn write_report_json_from_shared( + path: &Path, + policy: &crate::validation::policy::Policy, + validation_time: time::OffsetDateTime, + shared: &PostValidationShared, + format: ReportJsonFormat, +) -> Result { + use time::format_description::well_known::Rfc3339; + + let build_started = std::time::Instant::now(); + let validation_time_rfc3339_utc = validation_time + .to_offset(time::UtcOffset::UTC) + .format(&Rfc3339) + .expect("format validation_time"); + let repo_sync_stats = super::build_repo_sync_stats(shared.publication_points.as_ref()); + let query_audit = write_validation_events_sidecar(path, &validation_time_rfc3339_utc, shared)?; + let report = BorrowedAuditReportV2 { + format_version: 2, + meta: AuditRunMeta { + validation_time_rfc3339_utc, + }, + policy, + tree: BorrowedTreeSummary { + instances_processed: shared.instances_processed, + instances_failed: shared.instances_failed, + warnings: WarningReportSequence(shared.tree_warnings.as_ref()), + }, + publication_points: shared.publication_points.as_ref(), + vrps: VrpReportSequence(shared.vrps.as_ref()), + aspas: AspaReportSequence(shared.aspas.as_ref()), + downloads: shared.downloads.as_ref(), + download_stats: &shared.download_stats, + repo_sync_stats, + query_audit: Some(query_audit), + }; + let build_ms = build_started.elapsed().as_millis() as u64; + + let write_started = std::time::Instant::now(); + write_json(path, &report, format)?; + Ok(ReportJsonWriteTiming { + build_ms, + write_ms: write_started.elapsed().as_millis() as u64, + }) +} + +fn write_validation_events_sidecar( + report_path: &Path, + validation_time: &str, + shared: &PostValidationShared, +) -> Result { + let events_path = report_path.with_file_name("validation-events.jsonl"); + if let Some(parent) = events_path.parent() { + std::fs::create_dir_all(parent) + .map_err(|e| format!("create validation events parent failed: {e}"))?; + } + let mut writer = BufWriter::new(std::fs::File::create(&events_path).map_err(|e| { + format!( + "create validation events failed: {}: {e}", + events_path.display() + ) + })?); + let mut seq = 0u64; + let mut hasher = sha2::Sha256::new(); + emit_validation_events(validation_time, shared, &mut seq, &mut |event| { + let mut line = serde_json::to_vec(&event) + .map_err(|e| format!("serialize validation event failed: {e}"))?; + line.push(b'\n'); + std::io::Write::write_all(&mut writer, &line) + .map_err(|e| format!("write validation event failed: {e}"))?; + hasher.update(&line); + Ok(()) + })?; + std::io::Write::flush(&mut writer) + .map_err(|e| format!("flush validation events failed: {e}"))?; + let events_count = seq; + let events_sha256 = hex::encode(hasher.finalize()); + Ok(QueryAuditManifest { + schema_version: 1, + status: "complete".to_string(), + events_path: events_path + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or("validation-events.jsonl") + .to_string(), + events_count, + events_sha256, + writer_version: 1, + error: None, + }) +} + +fn emit_validation_events( + validation_time: &str, + shared: &PostValidationShared, + seq: &mut u64, + emit: &mut impl FnMut(ValidationEvent) -> Result<(), String>, +) -> Result<(), String> { + emit(next_event(seq, "run_summary", validation_time, |event| { + event.counts = Some(ValidationEventCounts { + objects: Some( + shared + .publication_points + .iter() + .map(|pp| pp.objects.len() as u64) + .sum(), + ), + warnings: Some( + (shared.tree_warnings.len() + + shared + .publication_points + .iter() + .map(|pp| pp.warnings.len()) + .sum::()) as u64, + ), + vrps: Some(shared.vrps.len() as u64), + aspas: Some(shared.aspas.len() as u64), + }); + }))?; + for pp in shared.publication_points.iter() { + emit(next_event( + seq, + "publication_point", + validation_time, + |event| { + event.pp_node_id = pp.node_id; + event.pp_manifest_uri = Some(pp.manifest_rsync_uri.clone()); + event.pp_rsync_base_uri = Some(pp.rsync_base_uri.clone()); + event.repo_sync_phase = pp.repo_sync_phase.clone(); + event.repo_terminal_state = Some(pp.repo_terminal_state.clone()); + event.counts = Some(ValidationEventCounts { + objects: Some(pp.objects.len() as u64), + warnings: Some(pp.warnings.len() as u64), + vrps: None, + aspas: None, + }); + }, + ))?; + for object in &pp.objects { + emit(next_event(seq, "object", validation_time, |event| { + event.pp_node_id = pp.node_id; + event.pp_manifest_uri = Some(pp.manifest_rsync_uri.clone()); + event.object_uri = Some(object.rsync_uri.clone()); + event.sha256 = Some(object.sha256_hex.clone()); + event.object_type = Some(object.kind.clone()); + event.result = Some(object.result.clone()); + event.reason = object.detail.clone(); + }))?; + } + for warning in &pp.warnings { + emit(next_event(seq, "warning", validation_time, |event| { + event.pp_node_id = pp.node_id; + event.pp_manifest_uri = Some(pp.manifest_rsync_uri.clone()); + event.reason = Some(warning.message.clone()); + }))?; + } + } + Ok(()) +} + +fn next_event( + seq: &mut u64, + event_type: &str, + validation_time: &str, + fill: impl FnOnce(&mut ValidationEvent), +) -> ValidationEvent { + *seq += 1; + let mut event = ValidationEvent { + schema_version: 1, + seq: *seq, + event_type: event_type.to_string(), + validation_time: validation_time.to_string(), + pp_node_id: None, + pp_manifest_uri: None, + pp_rsync_base_uri: None, + repo_sync_phase: None, + repo_terminal_state: None, + object_uri: None, + sha256: None, + object_type: None, + result: None, + reason: None, + counts: None, + }; + fill(&mut event); + event +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(super) struct CompareViewTaskOutput { + pub(super) build_ms: Option, + pub(super) write_ms: Option, +} + +pub(super) fn run_compare_view_task( + shared: &PostValidationShared, + vrps_csv_out_path: Option<&Path>, + vaps_csv_out_path: Option<&Path>, + trust_anchor: &str, +) -> Result { + let mut build_ms = None; + let mut write_ms = None; + if let (Some(vrps_path), Some(vaps_path)) = (vrps_csv_out_path, vaps_csv_out_path) { + let started = std::time::Instant::now(); + build_ms = Some(0); + write_direct_vrp_csv(vrps_path, shared.vrps.as_ref(), trust_anchor)?; + write_direct_vap_csv(vaps_path, shared.aspas.as_ref(), trust_anchor)?; + write_ms = Some(started.elapsed().as_millis() as u64); + crate::logging::info!( + "wrote compare views: vrps={} vaps={}", + vrps_path.display(), + vaps_path.display() + ); + } + Ok(CompareViewTaskOutput { build_ms, write_ms }) +} + +fn write_direct_vrp_csv( + path: &Path, + vrps: &[crate::validation::objects::Vrp], + trust_anchor: &str, +) -> Result<(), String> { + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent) + .map_err(|e| format!("create parent dirs failed: {}: {e}", parent.display()))?; + } + let file = std::fs::File::create(path) + .map_err(|e| format!("create file failed: {}: {e}", path.display()))?; + let mut writer = BufWriter::new(file); + use std::io::Write; + let trust_anchor = trust_anchor.to_ascii_lowercase(); + writeln!(writer, "ASN,IP Prefix,Max Length,Trust Anchor").map_err(|e| e.to_string())?; + for vrp in vrps { + writeln!( + writer, + "AS{},{},{},{}", + vrp.asn, + canonical_vrp_prefix(&vrp.prefix), + vrp.max_length, + trust_anchor + ) + .map_err(|e| e.to_string())?; + } + Ok(()) +} + +fn write_direct_vap_csv( + path: &Path, + aspas: &[crate::validation::objects::AspaAttestation], + trust_anchor: &str, +) -> Result<(), String> { + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent) + .map_err(|e| format!("create parent dirs failed: {}: {e}", parent.display()))?; + } + let file = std::fs::File::create(path) + .map_err(|e| format!("create file failed: {}: {e}", path.display()))?; + let mut writer = BufWriter::new(file); + use std::io::Write; + let trust_anchor = trust_anchor.to_ascii_lowercase(); + writeln!(writer, "Customer ASN,Providers,Trust Anchor").map_err(|e| e.to_string())?; + for aspa in aspas { + let mut providers = aspa.provider_as_ids.clone(); + providers.sort_unstable(); + providers.dedup(); + let providers = providers + .into_iter() + .map(|asn| format!("AS{asn}")) + .collect::>() + .join(";"); + writeln!( + writer, + "AS{},{},{}", + aspa.customer_as_id, providers, trust_anchor + ) + .map_err(|e| e.to_string())?; + } + Ok(()) +} + +pub(super) fn write_stage_timing( + report_json_path: Option<&Path>, + stage_timing: &RunStageTiming, +) -> Result<(), String> { + if let Some(path) = report_json_path + && let Some(parent) = path.parent() + { + let stage_timing_path = parent.join("stage-timing.json"); + std::fs::write( + &stage_timing_path, + serde_json::to_vec_pretty(stage_timing).map_err(|e| e.to_string())?, + ) + .map_err(|e| { + format!( + "write stage timing failed: {}: {e}", + stage_timing_path.display() + ) + })?; + crate::logging::info!("analysis: wrote {}", stage_timing_path.display()); + } + Ok(()) +} diff --git a/src/runtime/post_validation.rs b/src/runtime/post_validation.rs new file mode 100644 index 0000000..d778003 --- /dev/null +++ b/src/runtime/post_validation.rs @@ -0,0 +1,545 @@ +// Shared post-validation state and memory/report graph estimation. + +fn read_policy(path: Option<&Path>) -> Result { + match path { + None => Ok(Policy::default()), + Some(p) => { + let s = std::fs::read_to_string(p) + .map_err(|e| format!("read policy file failed: {}: {e}", p.display()))?; + Policy::from_toml_str(&s).map_err(|e| e.to_string()) + } + } +} + +fn unique_rrdp_repos_from_publication_points( + publication_points: &[crate::output::audit::PublicationPointAudit], +) -> usize { + use std::collections::HashSet; + let mut set: HashSet<&str> = HashSet::new(); + for pp in publication_points { + if let Some(u) = pp.rrdp_notification_uri.as_deref() { + set.insert(u); + } + } + set.len() +} + +fn print_summary_from_shared(validation_time: time::OffsetDateTime, shared: &PostValidationShared) { + let warning_count = shared.tree_warnings.len() + + shared.publication_points.iter().map(|pp| pp.warnings.len()).sum::(); + crate::logging::emit(crate::logging::Level::Info, "validation_summary", || serde_json::json!({ + "validation_time": validation_time.unix_timestamp(), + "publication_points": shared.instances_processed, "failed": shared.instances_failed, + "repositories": unique_rrdp_repos_from_publication_points(&shared.publication_points), + "vrps": shared.vrps.len(), "aspas": shared.aspas.len(), "warnings": warning_count, + })); + if warning_count > 0 { + crate::logging::emit(crate::logging::Level::Warn, "validation_warnings", || serde_json::json!({ + "count": warning_count, "failed_publication_points": shared.instances_failed, + })); + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct PostValidationShared { + discovery: crate::validation::from_tal::DiscoveredRootCaInstance, + discoveries: Arc<[crate::validation::from_tal::DiscoveredRootCaInstance]>, + successful_tal_inputs: Arc<[TalInputSpec]>, + instances_processed: usize, + instances_failed: usize, + tree_warnings: Arc<[crate::output::report::Warning]>, + vrps: Arc<[crate::validation::objects::Vrp]>, + aspas: Arc<[crate::validation::objects::AspaAttestation]>, + router_keys: Arc<[crate::validation::objects::RouterKeyPayload]>, + publication_points: Arc<[crate::output::audit::PublicationPointAudit]>, + downloads: Arc<[crate::output::audit::AuditDownloadEvent]>, + download_stats: crate::output::audit::AuditDownloadStats, + current_repo_objects: Arc<[crate::repository::current_repo_index::CurrentRepoObject]>, + ccr_accumulator: Option, +} + +impl PostValidationShared { + fn from_run_output(out: RunTreeFromTalAuditOutput) -> Self { + let RunTreeFromTalAuditOutput { + discovery, + discoveries, + successful_tal_inputs, + tree, + publication_points, + downloads, + download_stats, + current_repo_objects, + ccr_accumulator, + } = out; + let crate::validation::tree::TreeRunOutput { + instances_processed, + instances_failed, + warnings, + vrps, + aspas, + router_keys, + } = tree; + + Self { + discovery, + discoveries: discoveries.into(), + successful_tal_inputs: successful_tal_inputs.into(), + instances_processed, + instances_failed, + tree_warnings: warnings.into(), + vrps: vrps.into(), + aspas: aspas.into(), + router_keys: router_keys.into(), + publication_points: publication_points.into(), + downloads: downloads.into(), + download_stats, + current_repo_objects: current_repo_objects.into(), + ccr_accumulator, + } + } + +} + +#[derive(Default)] +struct ObjectGraphSectionBuilder { + name: String, + item_count: u64, + shallow_bytes: u64, + heap_bytes: u64, + string_count: u64, + string_bytes: u64, + string_capacity_bytes: u64, + vec_count: u64, + vec_heap_bytes: u64, + vec_capacity_bytes: u64, + details: Vec, +} + +impl ObjectGraphSectionBuilder { + fn new(name: impl Into) -> Self { + Self { + name: name.into(), + ..Self::default() + } + } + + fn items(&mut self, count: usize, item_size: usize) { + self.item_count += count as u64; + self.shallow_bytes += (count as u64) * (item_size as u64); + } + + fn heap_bytes(&mut self, value: usize) { + self.heap_bytes += value as u64; + } + + fn string(&mut self, value: &str) { + self.string_count += 1; + self.string_bytes += value.len() as u64; + self.string_capacity_bytes += value.len() as u64; + self.heap_bytes += value.len() as u64; + } + + fn owned_string(&mut self, value: &String) { + self.string_count += 1; + self.string_bytes += value.len() as u64; + self.string_capacity_bytes += value.capacity() as u64; + self.heap_bytes += value.capacity() as u64; + } + + fn optional_string(&mut self, value: Option<&String>) { + if let Some(value) = value { + self.owned_string(value); + } + } + + fn vec_header_with_capacity(&mut self, len: usize, capacity: usize, element_size: usize) { + self.vec_count += 1; + let payload_bytes = len * element_size; + let capacity_bytes = capacity * element_size; + self.vec_heap_bytes += payload_bytes as u64; + self.vec_capacity_bytes += capacity_bytes as u64; + self.heap_bytes += capacity_bytes as u64; + } + + fn byte_vec_owned(&mut self, value: &Vec) { + self.vec_header_with_capacity(value.len(), value.capacity(), std::mem::size_of::()); + } + + fn string_vec_owned(&mut self, values: &Vec) { + self.vec_header_with_capacity( + values.len(), + values.capacity(), + std::mem::size_of::(), + ); + for value in values { + self.owned_string(value); + } + } + + fn metric(&mut self, name: impl Into, value: u64) { + self.details.push(ObjectGraphMemoryMetric { + name: name.into(), + value, + }); + } + + fn finish(self) -> ObjectGraphMemorySection { + let estimated_bytes = self.shallow_bytes + self.heap_bytes; + ObjectGraphMemorySection { + name: self.name, + item_count: self.item_count, + shallow_bytes: self.shallow_bytes, + heap_bytes: self.heap_bytes, + estimated_bytes, + string_count: self.string_count, + string_bytes: self.string_bytes, + string_capacity_bytes: self.string_capacity_bytes, + vec_count: self.vec_count, + vec_heap_bytes: self.vec_heap_bytes, + vec_capacity_bytes: self.vec_capacity_bytes, + details: self.details, + } + } +} + +fn estimate_shared_object_graph(shared: &PostValidationShared) -> ObjectGraphMemorySummary { + let sections = vec![ + estimate_publication_points_graph(shared.publication_points.as_ref()), + estimate_vrps_graph(shared.vrps.as_ref()), + estimate_aspas_graph(shared.aspas.as_ref()), + estimate_router_keys_graph(shared.router_keys.as_ref()), + estimate_warnings_graph("tree_warnings", shared.tree_warnings.as_ref()), + estimate_downloads_graph(shared.downloads.as_ref()), + estimate_current_repo_objects_graph(shared.current_repo_objects.as_ref()), + estimate_trust_anchor_graph(shared), + estimate_ccr_accumulator_graph(shared.ccr_accumulator.as_ref()), + ]; + + let total_estimated_bytes = sections + .iter() + .map(|section| section.estimated_bytes) + .sum::(); + ObjectGraphMemorySummary { + captured_at_label: "after_validation".to_string(), + total_estimated_bytes, + sections, + notes: vec![ + "Estimated bytes are Rust object graph approximations based on struct sizes and owned String/Vec payload lengths.".to_string(), + "The estimate intentionally excludes allocator metadata, fragmentation, freed-but-retained arenas, RocksDB C++ heap, and transient worker allocations.".to_string(), + "Large RSS minus this estimate points to allocator retention or structures not yet modeled by this telemetry.".to_string(), + ], + } +} + +fn estimate_publication_points_graph( + publication_points: &[crate::output::audit::PublicationPointAudit], +) -> ObjectGraphMemorySection { + let mut builder = ObjectGraphSectionBuilder::new("publication_points"); + builder.items( + publication_points.len(), + std::mem::size_of::(), + ); + builder.metric("publication_point_count", publication_points.len() as u64); + let mut object_count = 0u64; + let mut pp_warning_count = 0u64; + let mut pp_discovered_from_count = 0u64; + let mut object_detail_count = 0u64; + + for pp in publication_points { + builder.owned_string(&pp.rsync_base_uri); + builder.owned_string(&pp.manifest_rsync_uri); + builder.owned_string(&pp.publication_point_rsync_uri); + builder.optional_string(pp.rrdp_notification_uri.as_ref()); + builder.owned_string(&pp.source); + builder.optional_string(pp.repo_sync_source.as_ref()); + builder.optional_string(pp.repo_sync_phase.as_ref()); + builder.optional_string(pp.repo_sync_error.as_ref()); + builder.owned_string(&pp.repo_terminal_state); + builder.owned_string(&pp.this_update_rfc3339_utc); + builder.owned_string(&pp.next_update_rfc3339_utc); + builder.owned_string(&pp.verified_at_rfc3339_utc); + + if let Some(discovered_from) = &pp.discovered_from { + pp_discovered_from_count += 1; + builder.heap_bytes(std::mem::size_of::()); + builder.owned_string(&discovered_from.parent_manifest_rsync_uri); + builder.owned_string(&discovered_from.child_ca_certificate_rsync_uri); + builder.owned_string(&discovered_from.child_ca_certificate_sha256_hex); + } + + pp_warning_count += pp.warnings.len() as u64; + builder.vec_header_with_capacity( + pp.warnings.len(), + pp.warnings.capacity(), + std::mem::size_of::(), + ); + for warning in &pp.warnings { + builder.owned_string(&warning.message); + builder.string_vec_owned(&warning.rfc_refs); + builder.optional_string(warning.context.as_ref()); + } + + object_count += pp.objects.len() as u64; + builder.vec_header_with_capacity( + pp.objects.len(), + pp.objects.capacity(), + std::mem::size_of::(), + ); + for object in &pp.objects { + builder.owned_string(&object.rsync_uri); + builder.owned_string(&object.sha256_hex); + if object.detail.is_some() { + object_detail_count += 1; + } + builder.optional_string(object.detail.as_ref()); + } + } + + builder.metric("object_audit_entry_count", object_count); + builder.metric("publication_point_warning_count", pp_warning_count); + builder.metric( + "publication_point_discovered_from_count", + pp_discovered_from_count, + ); + builder.metric("object_detail_count", object_detail_count); + builder.finish() +} + +fn estimate_vrps_graph(vrps: &[crate::validation::objects::Vrp]) -> ObjectGraphMemorySection { + let mut builder = ObjectGraphSectionBuilder::new("vrps"); + builder.items( + vrps.len(), + std::mem::size_of::(), + ); + builder.metric("vrp_count", vrps.len() as u64); + builder.finish() +} + +fn estimate_aspas_graph( + aspas: &[crate::validation::objects::AspaAttestation], +) -> ObjectGraphMemorySection { + let mut builder = ObjectGraphSectionBuilder::new("aspas"); + builder.items( + aspas.len(), + std::mem::size_of::(), + ); + let mut providers_total = 0u64; + for aspa in aspas { + providers_total += aspa.provider_as_ids.len() as u64; + builder.vec_header_with_capacity( + aspa.provider_as_ids.len(), + aspa.provider_as_ids.capacity(), + std::mem::size_of::(), + ); + } + builder.metric("aspa_count", aspas.len() as u64); + builder.metric("provider_asn_count", providers_total); + builder.finish() +} + +fn estimate_router_keys_graph( + router_keys: &[crate::validation::objects::RouterKeyPayload], +) -> ObjectGraphMemorySection { + let mut builder = ObjectGraphSectionBuilder::new("router_keys"); + builder.items( + router_keys.len(), + std::mem::size_of::(), + ); + for router_key in router_keys { + builder.byte_vec_owned(&router_key.ski); + builder.byte_vec_owned(&router_key.spki_der); + builder.owned_string(&router_key.source_object_uri); + builder.owned_string(&router_key.source_object_hash); + builder.owned_string(&router_key.source_ee_cert_hash); + } + builder.metric("router_key_count", router_keys.len() as u64); + builder.finish() +} + +fn estimate_warnings_graph( + name: &str, + warnings: &[crate::output::report::Warning], +) -> ObjectGraphMemorySection { + let mut builder = ObjectGraphSectionBuilder::new(name); + builder.items( + warnings.len(), + std::mem::size_of::(), + ); + for warning in warnings { + builder.owned_string(&warning.message); + builder.vec_header_with_capacity( + warning.rfc_refs.len(), + warning.rfc_refs.capacity(), + std::mem::size_of::(), + ); + builder.optional_string(warning.context.as_ref()); + } + builder.metric("warning_count", warnings.len() as u64); + builder.finish() +} + +fn estimate_downloads_graph( + downloads: &[crate::output::audit::AuditDownloadEvent], +) -> ObjectGraphMemorySection { + let mut builder = ObjectGraphSectionBuilder::new("downloads"); + builder.items( + downloads.len(), + std::mem::size_of::(), + ); + let mut error_count = 0u64; + let mut bytes_count = 0u64; + let mut objects_stat_count = 0u64; + for event in downloads { + builder.owned_string(&event.uri); + builder.owned_string(&event.started_at_rfc3339_utc); + builder.owned_string(&event.finished_at_rfc3339_utc); + if event.error.is_some() { + error_count += 1; + } + if event.bytes.is_some() { + bytes_count += 1; + } + if event.objects.is_some() { + objects_stat_count += 1; + } + builder.optional_string(event.error.as_ref()); + } + builder.metric("download_event_count", downloads.len() as u64); + builder.metric("download_error_count", error_count); + builder.metric("download_bytes_field_count", bytes_count); + builder.metric("download_objects_stat_count", objects_stat_count); + builder.finish() +} + +fn estimate_current_repo_objects_graph( + objects: &[crate::repository::current_repo_index::CurrentRepoObject], +) -> ObjectGraphMemorySection { + let mut builder = ObjectGraphSectionBuilder::new("current_repo_objects"); + builder.items( + objects.len(), + std::mem::size_of::(), + ); + let mut object_type_count = 0u64; + for object in objects { + builder.owned_string(&object.rsync_uri); + builder.owned_string(&object.current_hash_hex); + builder.owned_string(&object.repository_source); + if object.object_type.is_some() { + object_type_count += 1; + } + builder.optional_string(object.object_type.as_ref()); + } + builder.metric("current_repo_object_count", objects.len() as u64); + builder.metric("current_repo_object_type_count", object_type_count); + builder.finish() +} + +fn estimate_trust_anchor_graph(shared: &PostValidationShared) -> ObjectGraphMemorySection { + let mut builder = ObjectGraphSectionBuilder::new("trust_anchors_and_tal_inputs"); + builder.items( + 1, + std::mem::size_of::(), + ); + estimate_discovered_root(&mut builder, &shared.discovery); + builder.items( + shared.discoveries.len(), + std::mem::size_of::(), + ); + for discovery in shared.discoveries.iter() { + estimate_discovered_root(&mut builder, discovery); + } + builder.items( + shared.successful_tal_inputs.len(), + std::mem::size_of::(), + ); + for tal_input in shared.successful_tal_inputs.iter() { + estimate_tal_input(&mut builder, tal_input); + } + builder.metric("discoveries_count", shared.discoveries.len() as u64); + builder.metric( + "successful_tal_inputs_count", + shared.successful_tal_inputs.len() as u64, + ); + builder.finish() +} + +fn estimate_discovered_root( + builder: &mut ObjectGraphSectionBuilder, + discovery: &crate::validation::from_tal::DiscoveredRootCaInstance, +) { + builder.optional_string(discovery.tal_url.as_ref()); + estimate_trust_anchor(builder, &discovery.trust_anchor); + builder.owned_string(&discovery.ca_instance.rsync_base_uri); + builder.owned_string(&discovery.ca_instance.manifest_rsync_uri); + builder.owned_string(&discovery.ca_instance.publication_point_rsync_uri); + builder.optional_string(discovery.ca_instance.rrdp_notification_uri.as_ref()); +} + +fn estimate_trust_anchor( + builder: &mut ObjectGraphSectionBuilder, + trust_anchor: &crate::model::ta::TrustAnchor, +) { + builder.byte_vec_owned(&trust_anchor.tal.raw); + builder.string_vec_owned(&trust_anchor.tal.comments); + builder.vec_header_with_capacity( + trust_anchor.tal.ta_uris.len(), + trust_anchor.tal.ta_uris.capacity(), + std::mem::size_of::(), + ); + for uri in &trust_anchor.tal.ta_uris { + builder.string(uri.as_str()); + } + builder.byte_vec_owned(&trust_anchor.tal.subject_public_key_info_der); + builder.byte_vec_owned(&trust_anchor.ta_certificate.raw_der); + if let Some(uri) = &trust_anchor.resolved_ta_uri { + builder.string(uri.as_str()); + } +} + +fn estimate_tal_input(builder: &mut ObjectGraphSectionBuilder, tal_input: &TalInputSpec) { + builder.owned_string(&tal_input.tal_id); + builder.owned_string(&tal_input.rir_id); + match &tal_input.source { + crate::scheduler::types::TalSource::Url(url) => builder.owned_string(url), + crate::scheduler::types::TalSource::DerBytes { + tal_url, + tal_bytes, + ta_der, + } => { + builder.owned_string(tal_url); + builder.byte_vec_owned(tal_bytes); + builder.byte_vec_owned(ta_der); + } + crate::scheduler::types::TalSource::FilePath(path) => { + builder.string(&path.to_string_lossy()); + } + crate::scheduler::types::TalSource::FilePathWithTa { tal_path, ta_path } => { + builder.string(&tal_path.to_string_lossy()); + builder.string(&ta_path.to_string_lossy()); + } + } +} + +fn estimate_ccr_accumulator_graph( + accumulator: Option<&CcrAccumulator>, +) -> ObjectGraphMemorySection { + let mut builder = ObjectGraphSectionBuilder::new("ccr_accumulator"); + if let Some(accumulator) = accumulator { + builder.items(1, std::mem::size_of::()); + let stats = accumulator.memory_stats(); + builder.heap_bytes(stats.estimated_heap_bytes as usize); + builder.metric("trust_anchor_count", stats.trust_anchor_count); + builder.metric("manifest_count", stats.manifest_count); + builder.metric("string_bytes", stats.string_bytes); + builder.metric("string_capacity_bytes", stats.string_capacity_bytes); + builder.metric("vec_payload_bytes", stats.vec_payload_bytes); + builder.metric("vec_capacity_bytes", stats.vec_capacity_bytes); + builder.metric("locations_der_count", stats.locations_der_count); + builder.metric("subordinate_ski_count", stats.subordinate_ski_count); + builder.metric("btree_key_capacity_bytes", stats.btree_key_capacity_bytes); + builder.metric("btree_entry_shallow_bytes", stats.btree_entry_shallow_bytes); + } else { + builder.metric("manifest_count", 0); + } + builder.finish() +} diff --git a/src/runtime/report.rs b/src/runtime/report.rs new file mode 100644 index 0000000..3d6ed23 --- /dev/null +++ b/src/runtime/report.rs @@ -0,0 +1,32 @@ +// Audit report construction and online validation orchestration. + +fn run_online_validation_with_fetchers( + store: Arc, + policy: &Policy, + args: &RunConfig, + http: &H, + rsync: &R, + validation_time: time::OffsetDateTime, + config: &TreeRunConfig, + collect_current_repo_objects: bool, + timing: Option<&TimingHandle>, +) -> Result +where + H: crate::repository::sync::rrdp::Fetcher + Clone + 'static, + R: crate::repository::fetch::rsync::RsyncFetcher + Clone + 'static, +{ + // One scheduler entry preserves explicit TAL identities for one or many roots. + if let Some(t) = timing { + run_tree_from_multiple_tals_parallel_phase2_audit_with_timing( + store, policy, args.tal_inputs.clone(), http, rsync, validation_time, + config, args.parallel_phase1_config.clone(), args.parallel_phase2_config.clone(), + collect_current_repo_objects, t, + ) + } else { + run_tree_from_multiple_tals_parallel_phase2_audit( + store, policy, args.tal_inputs.clone(), http, rsync, validation_time, + config, args.parallel_phase1_config.clone(), args.parallel_phase2_config.clone(), + collect_current_repo_objects, + ) + }.map_err(|error| error.to_string()) +} diff --git a/src/runtime/report_tasks.rs b/src/runtime/report_tasks.rs new file mode 100644 index 0000000..7e11e9e --- /dev/null +++ b/src/runtime/report_tasks.rs @@ -0,0 +1,118 @@ +//! Isolated CLI output tasks. +//! +//! Report, CCR input selection, and repository-sync aggregation have no +//! dependency on network execution. Keeping them here lets the CLI +//! orchestrator own scheduling while these functions retain focused tests. + +use std::path::Path; + +use crate::ccr::write_ccr_file; +use crate::output::audit::AuditRepoSyncStats; +use crate::validation::policy::Policy; + +use super::PostValidationShared; +use super::output::{ReportJsonFormat, write_report_json_from_shared}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(super) struct ReportTaskOutput { + pub(super) report_build_ms: u64, + pub(super) report_write_ms: Option, +} + +impl ReportTaskOutput { + pub(super) fn skipped() -> Self { + Self { + report_build_ms: 0, + report_write_ms: None, + } + } +} + +pub(super) fn run_report_task( + policy: &Policy, + validation_time: time::OffsetDateTime, + shared: &PostValidationShared, + report_json_path: Option<&Path>, + report_json_format: ReportJsonFormat, +) -> Result { + if let Some(path) = report_json_path { + let timing = write_report_json_from_shared( + path, + policy, + validation_time, + shared, + report_json_format, + )?; + Ok(ReportTaskOutput { + report_build_ms: timing.build_ms, + report_write_ms: Some(timing.write_ms), + }) + } else { + Ok(ReportTaskOutput::skipped()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(super) struct CcrTaskOutput { + pub(super) ccr_build_ms: Option, + pub(super) ccr_write_ms: Option, +} + +pub(super) fn run_ccr_task( + shared: &PostValidationShared, + ccr_out_path: Option<&Path>, + produced_at: time::OffsetDateTime, +) -> Result { + let mut ccr_build_ms = None; + let mut ccr_write_ms = None; + if let Some(path) = ccr_out_path { + let started = std::time::Instant::now(); + let accumulator = shared.ccr_accumulator.as_ref().ok_or_else(|| { + "CCR accumulator missing; enable CCR accumulation for CCR output".to_string() + })?; + let ccr = accumulator + .finish( + produced_at, + shared.vrps.as_ref(), + shared.aspas.as_ref(), + shared.router_keys.as_ref(), + ) + .map_err(|e| e.to_string())?; + ccr_build_ms = Some(started.elapsed().as_millis() as u64); + let started = std::time::Instant::now(); + write_ccr_file(path, &ccr).map_err(|e| e.to_string())?; + ccr_write_ms = Some(started.elapsed().as_millis() as u64); + crate::logging::info!("wrote CCR: {}", path.display()); + } + + Ok(CcrTaskOutput { + ccr_build_ms, + ccr_write_ms, + }) +} + +pub(super) fn build_repo_sync_stats( + publication_points: &[crate::output::audit::PublicationPointAudit], +) -> AuditRepoSyncStats { + let mut stats = AuditRepoSyncStats { + publication_points_total: publication_points.len() as u64, + ..AuditRepoSyncStats::default() + }; + + for pp in publication_points { + let duration = pp.repo_sync_duration_ms.unwrap_or(0); + if let Some(phase) = pp.repo_sync_phase.as_ref() { + let entry = stats.by_phase.entry(phase.clone()).or_default(); + entry.count += 1; + entry.duration_ms_total += duration; + } + let entry = stats + .by_terminal_state + .entry(pp.repo_terminal_state.clone()) + .or_default(); + entry.count += 1; + entry.duration_ms_total += duration; + } + + stats +} diff --git a/src/runtime/run.rs b/src/runtime/run.rs new file mode 100644 index 0000000..2a1d599 --- /dev/null +++ b/src/runtime/run.rs @@ -0,0 +1,548 @@ +// Top-level CLI execution pipeline. + +pub(crate) fn run_config(args: RunConfig) -> Result<(), String> { + let mut policy = read_policy(args.policy_path.as_deref())?; + if let Some(strict_policy) = args.strict_policy { + policy.strict = strict_policy; + } + if let Some(resource_validation_mode) = args.resource_validation_mode { + policy.resource_validation_mode = resource_validation_mode; + } + if args.disable_rrdp { + policy.sync_preference = crate::validation::policy::SyncPreference::RsyncOnly; + } + policy.ta_constraints = args.ta_constraints.clone(); + for warning in policy.ta_constraints.configuration_warnings() { + crate::logging::emit(crate::logging::Level::Warn, "constraint_warning", || serde_json::json!({"reason": warning})); + } + let validation_time = args + .validation_time + .unwrap_or_else(time::OffsetDateTime::now_utc); + let validation_time = + time::OffsetDateTime::from_unix_timestamp(validation_time.unix_timestamp()) + .map_err(|error| format!("normalize validation time failed: {error}"))?; + let http_root_certificates_pem = args + .http_root_cert_paths + .iter() + .map(|path| { + std::fs::read(path) + .map_err(|e| format!("read HTTP root certificate failed: {}: {e}", path.display())) + }) + .collect::, _>>()?; + + let store = if args.raw_store_db.is_some() || args.repo_bytes_db.is_some() { + Arc::new( + RocksStore::open_with_external_stores( + &args.db_path, + args.raw_store_db.as_deref(), + args.repo_bytes_db.as_deref(), + ) + .map_err(|e| e.to_string())?, + ) + } else { + Arc::new(RocksStore::open(&args.db_path).map_err(|e| e.to_string())?) + }; + let config = TreeRunConfig { + max_depth: Some(args.max_ca_depth), + max_instances: args.max_instances, + compact_audit: args.skip_report_build + && args.report_json_path.is_none(), + build_ccr_accumulator: args.ccr_out_path.is_some(), + }; + + use time::format_description::well_known::Rfc3339; + let mut timing: Option<(std::path::PathBuf, TimingHandle)> = None; + if args.analyze { + let recorded_at_utc_rfc3339 = time::OffsetDateTime::now_utc() + .to_offset(time::UtcOffset::UTC) + .format(&Rfc3339) + .map_err(|e| format!("format recorded_at_utc failed: {e}"))?; + let validation_time_utc_rfc3339 = validation_time + .to_offset(time::UtcOffset::UTC) + .format(&Rfc3339) + .map_err(|e| format!("format validation_time failed: {e}"))?; + + let ts_compact = { + let fmt = time::format_description::parse_borrowed::<2>( + "[year][month][day]T[hour][minute][second]Z", + ) + .map_err(|e| format!("format description parse failed: {e}"))?; + time::OffsetDateTime::now_utc() + .format(&fmt) + .map_err(|e| format!("format timestamp failed: {e}"))? + }; + + let out_dir = args.analysis_out_path.clone().unwrap_or_else(|| { + std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("target") + .join("live") + .join("analyze") + .join(ts_compact) + }); + std::fs::create_dir_all(&out_dir) + .map_err(|e| format!("create analyze out dir failed: {}: {e}", out_dir.display()))?; + + let handle = TimingHandle::new(TimingMeta { + recorded_at_utc_rfc3339, + validation_time_utc_rfc3339, + tal_url: None, + db_path: None, + }); + handle.set_meta(TimingMetaUpdate { + tal_url: args.tal_url.as_deref(), + db_path: Some(args.db_path.to_string_lossy().as_ref()), + }); + timing = Some((out_dir, handle)); + } + + let total_started = std::time::Instant::now(); + let mut memory_checkpoints: Vec = Vec::new(); + let mut malloc_trim_probes: Vec = Vec::new(); + let enable_memory_trim_probe = memory_trim_probe_enabled() || args.memory_trim_after_validation; + record_memory_checkpoint( + &mut memory_checkpoints, + "after_store_open", + &total_started, + store.as_ref(), + ); + let validation_started = std::time::Instant::now(); + let collect_current_repo_objects = false; + let out = if let Some(dir) = args.rsync_local_dir.as_ref() { + let http = BlockingHttpFetcher::new(HttpFetcherConfig { + timeout: std::time::Duration::from_secs(args.http_timeout_secs.max(1)), + extra_root_certificates_pem: http_root_certificates_pem.clone(), + ..HttpFetcherConfig::default() + }) + .map_err(|e| e.to_string())?; + let rsync = LocalDirRsyncFetcher::new(dir); + run_online_validation_with_fetchers( + Arc::clone(&store), + &policy, + &args, + &http, + &rsync, + validation_time, + &config, + collect_current_repo_objects, + timing.as_ref().map(|(_, t)| t), + )? + } else { + let http = BlockingHttpFetcher::new(HttpFetcherConfig { + timeout: std::time::Duration::from_secs(args.http_timeout_secs.max(1)), + extra_root_certificates_pem: http_root_certificates_pem.clone(), + ..HttpFetcherConfig::default() + }) + .map_err(|e| e.to_string())?; + let rsync = SystemRsyncFetcher::new(SystemRsyncConfig { + rsync_bin: args + .rsync_command + .clone() + .unwrap_or_else(|| PathBuf::from("rsync")), + timeout: std::time::Duration::from_secs(args.rsync_timeout_secs.max(1)), + mirror_root: args.rsync_mirror_root.clone(), + scope_policy: args.rsync_scope_policy, + ..SystemRsyncConfig::default() + }); + run_online_validation_with_fetchers( + Arc::clone(&store), + &policy, + &args, + &http, + &rsync, + validation_time, + &config, + collect_current_repo_objects, + timing.as_ref().map(|(_, t)| t), + )? + }; + + let validation_ms = validation_started.elapsed().as_millis() as u64; + crate::logging::emit(crate::logging::Level::Info, "validation_phase_completed", || serde_json::json!({"elapsed_ms": validation_ms})); + let shared = PostValidationShared::from_run_output(out); + record_memory_checkpoint( + &mut memory_checkpoints, + "after_validation", + &total_started, + store.as_ref(), + ); + if enable_memory_trim_probe { + malloc_trim_probes.push(crate::output::memory::malloc_trim_probe()); + record_memory_checkpoint( + &mut memory_checkpoints, + "after_validation_malloc_trim", + &total_started, + store.as_ref(), + ); + } + + if let Some((_out_dir, t)) = timing.as_ref() { + t.record_count("instances_processed", shared.instances_processed as u64); + t.record_count("instances_failed", shared.instances_failed as u64); + } + + let publication_points = shared.publication_points.len(); + let publication_point_repo_sync_ms_total: u64 = shared + .publication_points + .iter() + .map(|pp| pp.repo_sync_duration_ms.unwrap_or(0)) + .sum(); + let download_event_count = shared.download_stats.events_total; + let rrdp_download_ms_total: u64 = ["rrdp_notification", "rrdp_snapshot", "rrdp_delta"] + .iter() + .map(|key| { + shared + .download_stats + .by_kind + .get(*key) + .map(|item| item.duration_ms_total) + .unwrap_or(0) + }) + .sum(); + let rsync_download_ms_total = shared + .download_stats + .by_kind + .get("rsync") + .map(|item| item.duration_ms_total) + .unwrap_or(0); + let repo_sync_ms_total = rrdp_download_ms_total + rsync_download_ms_total; + let download_bytes_total: u64 = shared + .download_stats + .by_kind + .values() + .map(|item| item.bytes_total.unwrap_or(0)) + .sum(); + + let report_json_format = if args.report_json_compact { + ReportJsonFormat::Compact + } else { + ReportJsonFormat::Pretty + }; + let ccr_produced_at = time::OffsetDateTime::now_utc(); + let compare_view_trust_anchor = args + .compare_view_trust_anchor + .as_deref() + .unwrap_or("unknown"); + let (report_result, ccr_result, compare_view_result) = + std::thread::scope(|scope| { + // Reborrow `shared` as a plain reference so the scoped output tasks + // capture the reference instead of moving fields out of the owned value. + let shared = &shared; + let report_handle = if args.skip_report_build { + None + } else { + Some(scope.spawn(|| { + run_report_task( + &policy, + validation_time, + shared, + args.report_json_path.as_deref(), + report_json_format, + ) + })) + }; + let ccr_handle = scope.spawn(|| { + run_ccr_task( + shared, + args.ccr_out_path.as_deref(), + ccr_produced_at, + ) + }); + let compare_view_handle = scope.spawn(|| { + run_compare_view_task( + shared, + args.vrps_csv_out_path.as_deref(), + args.vaps_csv_out_path.as_deref(), + compare_view_trust_anchor, + ) + }); + let report_result = match report_handle { + Some(handle) => handle + .join() + .map_err(|_| "report task panicked".to_string()) + .and_then(|result| result), + None => Ok(ReportTaskOutput::skipped()), + }; + let ccr_result = ccr_handle + .join() + .map_err(|_| "ccr task panicked".to_string()) + .and_then(|result| result); + let compare_view_result = compare_view_handle + .join() + .map_err(|_| "compare view task panicked".to_string()) + .and_then(|result| result); + (report_result, ccr_result, compare_view_result) + }); + let report_output = report_result?; + let ccr_output = ccr_result?; + let compare_view_output = compare_view_result?; + record_memory_checkpoint( + &mut memory_checkpoints, + "after_report_and_ccr", + &total_started, + store.as_ref(), + ); + if enable_memory_trim_probe { + malloc_trim_probes.push(crate::output::memory::malloc_trim_probe()); + record_memory_checkpoint( + &mut memory_checkpoints, + "after_report_and_ccr_malloc_trim", + &total_started, + store.as_ref(), + ); + } + let report_build_ms = report_output.report_build_ms; + let report_write_ms = report_output.report_write_ms; + let ccr_build_ms = ccr_output.ccr_build_ms; + let ccr_write_ms = ccr_output.ccr_write_ms; + let compare_view_build_ms = compare_view_output.build_ms; + let compare_view_write_ms = compare_view_output.write_ms; + record_memory_checkpoint( + &mut memory_checkpoints, + "after_compare_view", + &total_started, + store.as_ref(), + ); + + record_memory_checkpoint( + &mut memory_checkpoints, + "before_stage_timing", + &total_started, + store.as_ref(), + ); + let timing_report_snapshot = timing + .as_ref() + .map(|(_, handle)| handle.report_snapshot(50)); + let stage_timing = RunStageTiming { + validation_ms, + report_build_ms, + report_write_ms, + ccr_build_ms, + ccr_write_ms, + compare_view_build_ms, + compare_view_write_ms, + total_ms: total_started.elapsed().as_millis() as u64, + publication_points, + repo_sync_ms_total, + publication_point_repo_sync_ms_total, + download_event_count, + rrdp_download_ms_total, + rsync_download_ms_total, + download_bytes_total, + analysis_counts: timing + .as_ref() + .map(|(_, handle)| handle.counts_snapshot()) + .unwrap_or_default(), + analysis_phases: timing_report_snapshot + .as_ref() + .map(|report| report.phases.clone()) + .unwrap_or_default(), + analysis_top_publication_points: timing_report_snapshot + .as_ref() + .map(|report| report.top_publication_points.clone()) + .unwrap_or_default(), + analysis_top_publication_point_steps: timing_report_snapshot + .as_ref() + .map(|report| report.top_publication_point_steps.clone()) + .unwrap_or_default(), + memory_telemetry: Some(MemoryTelemetrySummary { + checkpoints: memory_checkpoints, + object_graph: Some(estimate_shared_object_graph(&shared)), + malloc_trim_probes, + }), + }; + let stage_timing_anchor_path = args + .report_json_path + .as_deref() + .or(args.ccr_out_path.as_deref()) + .or(args.vrps_csv_out_path.as_deref()); + write_stage_timing(stage_timing_anchor_path, &stage_timing)?; + + if let Some((out_dir, t)) = timing.as_ref() { + t.record_count("vrps", shared.vrps.len() as u64); + t.record_count("aspas", shared.aspas.len() as u64); + t.record_count( + "audit_publication_points", + shared.publication_points.len() as u64, + ); + let timing_json_path = out_dir.join("timing.json"); + t.write_json(&timing_json_path, 20)?; + crate::logging::info!("analysis: wrote {}", timing_json_path.display()); + } + + // Write the compatibility summary after analysis timing has been flushed. + // The RRDP counters (notably delta operations) are recorded by + // the transport/validation stages rather than by the compact publication + // point audit, so the final summary can use those authoritative counts. + if let Some(path) = args.summary_out_path.as_deref() { + let analysis_counts = timing + .as_ref() + .map(|(_, handle)| handle.counts_snapshot()); + write_summary( + path, + &shared, + &args, + validation_ms, + analysis_counts.as_ref(), + )?; + } + + print_summary_from_shared(validation_time, &shared); + Ok(()) +} + +#[derive(Serialize)] +struct ValidationSummary { + backend: &'static str, + worker_count: usize, + repo_sync_worker_count: usize, + trust_anchors: usize, + synchronized_repositories: usize, + synchronized_objects: usize, + validated_ca_certificates: usize, + validated_manifests: usize, + validated_crls: usize, + validated_roas: usize, + rejected_ca_certificates: usize, + rejected_manifests: usize, + rejected_crls: usize, + rejected_roas: usize, + rrdp_snapshot_repositories: usize, + rrdp_snapshot_fallbacks: usize, + rrdp_delta_repositories: usize, + rrdp_noop_repositories: usize, + rrdp_delta_updates: usize, + vrps: usize, + publication_points: usize, + validation_ms: u64, +} + +fn write_summary( + path: &Path, + shared: &PostValidationShared, + args: &RunConfig, + validation_ms: u64, + analysis_counts: Option<&std::collections::HashMap>, +) -> Result<(), String> { + use crate::output::audit::{AuditDownloadKind, AuditObjectKind, AuditObjectResult}; + use std::collections::BTreeSet; + + let mut summary = ValidationSummary { + backend: "panda-rpki", + worker_count: args.parallel_phase2_config.object_workers, + repo_sync_worker_count: args.parallel_phase1_config.max_repo_sync_workers_global, + trust_anchors: shared.discoveries.len().max(1), + synchronized_repositories: 0, + synchronized_objects: 0, + validated_ca_certificates: shared.instances_processed, + validated_manifests: 0, + validated_crls: 0, + validated_roas: 0, + rejected_ca_certificates: shared.instances_failed, + rejected_manifests: 0, + rejected_crls: 0, + rejected_roas: 0, + rrdp_snapshot_repositories: 0, + rrdp_snapshot_fallbacks: 0, + rrdp_delta_repositories: 0, + rrdp_noop_repositories: 0, + rrdp_delta_updates: 0, + vrps: shared.vrps.len(), + publication_points: shared.publication_points.len(), + validation_ms, + }; + let mut notification_uris = BTreeSet::new(); + for point in shared.publication_points.iter() { + if let Some(uri) = point.rrdp_notification_uri.as_deref() { + notification_uris.insert(uri); + } + summary.synchronized_objects += point.objects.len(); + match point.repo_sync_phase.as_deref() { + Some(phase) if phase.contains("fallback") => summary.rrdp_snapshot_fallbacks += 1, + Some(phase) if phase.contains("snapshot") => { + summary.rrdp_snapshot_repositories += 1 + } + Some(phase) if phase.contains("delta") => summary.rrdp_delta_repositories += 1, + Some(phase) if phase.contains("noop") => summary.rrdp_noop_repositories += 1, + _ => {} + } + for object in &point.objects { + let accepted = object.result == AuditObjectResult::Ok; + match &object.kind { + AuditObjectKind::Manifest => { + if accepted { summary.validated_manifests += 1; } else { summary.rejected_manifests += 1; } + } + AuditObjectKind::Crl => { + if accepted { summary.validated_crls += 1; } else { summary.rejected_crls += 1; } + } + AuditObjectKind::Roa => { + if accepted { summary.validated_roas += 1; } else { summary.rejected_roas += 1; } + } + _ => {} + } + } + } + // Compact audit mode is not used by the Panda RPKI frontend, but deriving the + // transport counters from download statistics keeps the summary correct + // even when a future caller chooses a reduced publication-point audit. + let download_stat = |kind: &str| shared.download_stats.by_kind.get(kind); + if let Some(stats) = download_stat("rrdp_snapshot") { + summary.rrdp_snapshot_repositories = stats.ok_total as usize; + } + if let Some(stats) = download_stat("rrdp_delta") { + summary.rrdp_delta_repositories = stats.ok_total as usize; + let counted_delta_updates = stats.objects_count_total.unwrap_or(0); + summary.rrdp_delta_updates = if counted_delta_updates > 0 { + counted_delta_updates as usize + } else { + analysis_counts + .and_then(|counts| counts.get("rrdp_delta_ops_applied_total")) + .copied() + .unwrap_or_else(|| { + shared + .downloads + .iter() + .filter(|event| event.kind == AuditDownloadKind::RrdpDelta && event.success) + .filter_map(|event| event.objects.as_ref()) + .map(|objects| objects.objects_count) + .sum() + }) as usize + }; + } + if let Some(stats) = download_stat("rsync") { + summary.rrdp_snapshot_fallbacks = stats.ok_total as usize; + } + if let Some(stats) = download_stat("rrdp_notification") { + let transport_repositories = stats.ok_total as usize; + summary.rrdp_noop_repositories = transport_repositories + .saturating_sub(summary.rrdp_snapshot_repositories) + .saturating_sub(summary.rrdp_delta_repositories) + .saturating_sub(summary.rrdp_snapshot_fallbacks); + } + let downloaded_objects = analysis_counts + .and_then(|counts| { + let snapshot = counts.get("rrdp_snapshot_objects_applied_total").copied(); + let delta = counts.get("rrdp_delta_ops_applied_total").copied(); + snapshot.zip(delta).map(|(snapshot, delta)| snapshot + delta) + }) + .unwrap_or_else(|| { + shared + .download_stats + .by_kind + .values() + .filter_map(|stats| stats.objects_count_total) + .sum() + }); + if downloaded_objects > 0 { + summary.synchronized_objects = downloaded_objects as usize; + } + summary.synchronized_repositories = notification_uris.len(); + + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent) + .map_err(|error| format!("create Panda RPKI summary parent {}: {error}", parent.display()))?; + } + let bytes = serde_json::to_vec_pretty(&summary) + .map_err(|error| format!("serialize Panda RPKI summary: {error}"))?; + std::fs::write(path, bytes) + .map_err(|error| format!("write Panda RPKI summary {}: {error}", path.display())) +} diff --git a/src/runtime/tests.rs b/src/runtime/tests.rs new file mode 100644 index 0000000..318cd3d --- /dev/null +++ b/src/runtime/tests.rs @@ -0,0 +1,169 @@ +use super::*; +use std::io::{BufRead, BufReader}; +use std::process::{Child, Command, Stdio}; + +struct ChildGuard(Child); +impl Drop for ChildGuard { + fn drop(&mut self) { + let _ = self.0.kill(); + let _ = self.0.wait(); + } +} + +fn free_port() -> u16 { + std::net::TcpListener::bind("127.0.0.1:0") + .unwrap() + .local_addr() + .unwrap() + .port() +} + +#[test] +fn synthetic_snapshot_delta_restart_rollback_and_log_equivalence() { + let tmp = tempfile::tempdir().unwrap(); + let fixture = tmp.path().join("fixture"); + let port = free_port(); + assert!( + Command::new("python3") + .args([ + "tests/support/generate_repository.py", + "--rrdp-host", + "127.0.0.1", + "--rrdp-port", + &port.to_string(), + "--output" + ]) + .arg(&fixture) + .status() + .unwrap() + .success() + ); + let active = tmp.path().join("active"); + std::os::unix::fs::symlink(fixture.join("cases/baseline-v1/http"), &active).unwrap(); + let mut server = ChildGuard( + Command::new("python3") + .arg("tests/support/serve_repository.py") + .arg(&active) + .arg(port.to_string()) + .arg(fixture.join("certs/rrdp-server.pem")) + .arg(fixture.join("certs/rrdp-server.key")) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .spawn() + .unwrap(), + ); + let mut ready = String::new(); + BufReader::new(server.0.stdout.take().unwrap()) + .read_line(&mut ready) + .unwrap(); + assert_eq!(ready.trim(), "ready"); + let db = tmp.path().join("state"); + let run = |name: &str, expected_vrps: u64, workers: usize, log: &str| { + let out = tmp.path().join(name); + std::fs::create_dir_all(&out).unwrap(); + crate::logging::configure(log, "json").unwrap(); + let input = TalInputSpec::from_file_path_with_ta( + fixture.join("tal/custom.tal"), + fixture.join("ta/custom-ta.cer"), + ); + run_config(RunConfig { + db_path: db.clone(), + tal_inputs: vec![input], + http_root_cert_paths: vec![fixture.join("certs/rrdp-ca.pem")], + http_timeout_secs: 2, + rsync_timeout_secs: 1, + max_ca_depth: 64, + validation_time: Some(time::OffsetDateTime::from_unix_timestamp(1788825600).unwrap()), + summary_out_path: Some(out.join("summary.json")), + ccr_out_path: Some(out.join("result.ccr")), + vrps_csv_out_path: Some(out.join("vrps.csv")), + vaps_csv_out_path: Some(out.join("vaps.csv")), + report_json_path: Some(out.join("report.json")), + analyze: true, + analysis_out_path: Some(out.join("analysis")), + parallel_phase2_config: ParallelPhase2Config { + object_workers: workers, + ..Default::default() + }, + ..Default::default() + }) + .unwrap(); + let summary: serde_json::Value = + serde_json::from_slice(&std::fs::read(out.join("summary.json")).unwrap()).unwrap(); + assert_eq!(summary["vrps"], expected_vrps, "{summary}"); + let ccr = crate::ccr::decode_content_info(&std::fs::read(out.join("result.ccr")).unwrap()) + .unwrap(); + crate::ccr::verify_content_info(&ccr).unwrap(); + assert_eq!( + crate::ccr::extract_vrp_rows(&ccr).unwrap().len(), + expected_vrps as usize + ); + (out, summary) + }; + let (first, _) = run("snapshot", 1, 1, "info"); + let (second, _) = run("restart-off", 1, 8, "off"); + assert_eq!( + std::fs::read(first.join("vrps.csv")).unwrap(), + std::fs::read(second.join("vrps.csv")).unwrap() + ); + let uri = "rsync://rpki-local-rsync:873/custom/child/child.mft"; + let before = { + let store = RocksStore::open(&db).unwrap(); + store + .get_manifest_anti_rollback_meta(uri) + .unwrap() + .expect("runtime must persist freshness") + }; + assert_eq!(before.manifest_number_be, vec![1]); + std::fs::remove_file(&active).unwrap(); + std::os::unix::fs::symlink(fixture.join("cases/baseline-v2/http"), &active).unwrap(); + let (delta, _) = run("delta", 2, 8, "info"); + let stage: serde_json::Value = + serde_json::from_slice(&std::fs::read(delta.join("stage-timing.json")).unwrap()).unwrap(); + assert!( + stage["analysis_counts"]["rrdp_delta_ops_applied_total"] + .as_u64() + .unwrap_or(0) + > 0, + "delta transport was not applied" + ); + let after = { + let store = RocksStore::open(&db).unwrap(); + store.get_manifest_anti_rollback_meta(uri).unwrap().unwrap() + }; + assert_eq!(after.manifest_number_be, vec![2]); + // Use a fresh RRDP session to force fetching an older signed manifest. + // The manifest guard must survive an otherwise legitimate transport reset. + let rollback = tmp.path().join("rollback"); + std::fs::create_dir_all(rollback.join("rrdp")).unwrap(); + let old = fixture.join("cases/baseline-v1/http/rrdp"); + let snap = std::fs::read_to_string(old.join("snapshot.xml")) + .unwrap() + .replace( + "11111111-2222-4333-8444-555555555555", + "22222222-2222-4333-8444-555555555555", + ); + let old_snap = std::fs::read(old.join("snapshot.xml")).unwrap(); + use sha2::Digest; + let notification = std::fs::read_to_string(old.join("notification.xml")) + .unwrap() + .replace( + "11111111-2222-4333-8444-555555555555", + "22222222-2222-4333-8444-555555555555", + ) + .replace( + &hex::encode(sha2::Sha256::digest(&old_snap)), + &hex::encode(sha2::Sha256::digest(snap.as_bytes())), + ); + std::fs::write(rollback.join("rrdp/snapshot.xml"), snap).unwrap(); + std::fs::write(rollback.join("rrdp/notification.xml"), notification).unwrap(); + std::fs::remove_file(&active).unwrap(); + std::os::unix::fs::symlink(&rollback, &active).unwrap(); + run("rollback-rejected", 0, 8, "info"); + let store = RocksStore::open(&db).unwrap(); + assert_eq!( + store.get_manifest_anti_rollback_meta(uri).unwrap(), + Some(after) + ); + crate::logging::configure("info", "text").unwrap(); +} diff --git a/src/runtime/types.rs b/src/runtime/types.rs new file mode 100644 index 0000000..29e3036 --- /dev/null +++ b/src/runtime/types.rs @@ -0,0 +1,93 @@ +// CLI timing and argument data types. + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +struct RunStageTiming { + validation_ms: u64, + report_build_ms: u64, + report_write_ms: Option, + ccr_build_ms: Option, + ccr_write_ms: Option, + compare_view_build_ms: Option, + compare_view_write_ms: Option, + total_ms: u64, + publication_points: usize, + repo_sync_ms_total: u64, + publication_point_repo_sync_ms_total: u64, + download_event_count: u64, + rrdp_download_ms_total: u64, + rsync_download_ms_total: u64, + download_bytes_total: u64, + analysis_counts: HashMap, + analysis_phases: HashMap, + analysis_top_publication_points: Vec, + analysis_top_publication_point_steps: Vec, + memory_telemetry: Option, +} + +fn record_memory_checkpoint( + checkpoints: &mut Vec, + label: &str, + total_started: &std::time::Instant, + store: &RocksStore, +) { + checkpoints.push(MemoryTelemetryCheckpoint { + label: label.to_string(), + elapsed_ms: total_started.elapsed().as_millis() as u64, + process: crate::output::memory::process_memory_snapshot(label), + rocksdb: store.memory_snapshot(), + }); +} + +fn memory_trim_probe_enabled() -> bool { + std::env::var("RPKI_MEMORY_TRIM_PROBE") + .map(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "YES")) + .unwrap_or(false) +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct RunConfig { + /// Validation summary output path. + pub summary_out_path: Option, + pub tal_urls: Vec, + pub tal_paths: Vec, + pub ta_paths: Vec, + pub tal_url: Option, + pub tal_path: Option, + pub ta_path: Option, + pub parallel_phase1_config: ParallelPhase1Config, + pub parallel_phase2_config: ParallelPhase2Config, + pub tal_inputs: Vec, + pub ta_constraints: TaConstraintsByTal, + + pub db_path: PathBuf, + pub raw_store_db: Option, + pub repo_bytes_db: Option, + pub policy_path: Option, + pub strict_policy: Option, + pub resource_validation_mode: Option, + pub report_json_path: Option, + pub report_json_compact: bool, + pub skip_report_build: bool, + pub ccr_out_path: Option, + pub vrps_csv_out_path: Option, + pub vaps_csv_out_path: Option, + pub compare_view_trust_anchor: Option, + pub memory_trim_after_validation: bool, + + pub rsync_local_dir: Option, + pub disable_rrdp: bool, + pub rsync_command: Option, + + pub http_timeout_secs: u64, + pub http_root_cert_paths: Vec, + pub rsync_timeout_secs: u64, + pub rsync_mirror_root: Option, + pub rsync_scope_policy: RsyncScopePolicy, + + pub max_ca_depth: usize, + pub max_instances: Option, + pub validation_time: Option, + + pub analyze: bool, + pub analysis_out_path: Option, +} diff --git a/src/scheduler/config.rs b/src/scheduler/config.rs new file mode 100644 index 0000000..52bec45 --- /dev/null +++ b/src/scheduler/config.rs @@ -0,0 +1,73 @@ +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ParallelPhase1Config { + pub max_repo_sync_workers_global: usize, + pub max_inflight_snapshot_bytes_global: usize, + pub max_pending_repo_results: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ParallelPhase2Config { + pub object_workers: usize, + pub worker_queue_capacity: usize, + pub ready_batch_size: usize, + pub ready_batch_wall_time_budget_ms: u64, + pub object_result_drain_batch_size: usize, + pub publication_point_finalize_batch_size: usize, + pub publication_point_finalize_wall_time_budget_ms: u64, + pub publication_point_finalize_queue_capacity: usize, + /// Experimental: workers of the ready publication point stage pool. `0` + /// disables the pool and keeps the inline compute+apply staging path. + pub stage_workers: usize, +} + +impl Default for ParallelPhase2Config { + fn default() -> Self { + Self { + object_workers: 8, + worker_queue_capacity: 256, + ready_batch_size: 256, + ready_batch_wall_time_budget_ms: 100, + object_result_drain_batch_size: 2048, + publication_point_finalize_batch_size: 256, + publication_point_finalize_wall_time_budget_ms: 100, + publication_point_finalize_queue_capacity: 32768, + stage_workers: 0, + } + } +} + +impl Default for ParallelPhase1Config { + fn default() -> Self { + Self { + max_repo_sync_workers_global: 4, + max_inflight_snapshot_bytes_global: 512 * 1024 * 1024, + max_pending_repo_results: 1024, + } + } +} + +#[cfg(test)] +mod tests { + use super::{ParallelPhase1Config, ParallelPhase2Config}; + + #[test] + fn default_parallel_phase1_config_is_bounded() { + let cfg = ParallelPhase1Config::default(); + assert!(cfg.max_repo_sync_workers_global > 0); + assert!(cfg.max_inflight_snapshot_bytes_global > 0); + assert!(cfg.max_pending_repo_results > 0); + } + + #[test] + fn default_parallel_phase2_config_is_bounded() { + let cfg = ParallelPhase2Config::default(); + assert!(cfg.object_workers > 0); + assert!(cfg.worker_queue_capacity > 0); + assert!(cfg.ready_batch_size > 0); + assert!(cfg.ready_batch_wall_time_budget_ms > 0); + assert!(cfg.object_result_drain_batch_size > 0); + assert!(cfg.publication_point_finalize_batch_size > 0); + assert!(cfg.publication_point_finalize_wall_time_budget_ms > 0); + assert!(cfg.publication_point_finalize_queue_capacity > 0); + } +} diff --git a/src/scheduler/mod.rs b/src/scheduler/mod.rs new file mode 100644 index 0000000..4fe1ab1 --- /dev/null +++ b/src/scheduler/mod.rs @@ -0,0 +1,9 @@ +pub mod config; +pub mod object_worker; +pub mod phase2_scheduler; +pub mod repo_runtime; +pub mod repo_scheduler; +pub mod repo_worker; +pub mod run_coordinator; +pub mod stats; +pub mod types; diff --git a/src/scheduler/object_worker.rs b/src/scheduler/object_worker.rs new file mode 100644 index 0000000..6fe055d --- /dev/null +++ b/src/scheduler/object_worker.rs @@ -0,0 +1,480 @@ +use std::marker::PhantomData; +use std::sync::Arc; +use std::sync::mpsc::{self, Receiver, RecvTimeoutError, SyncSender, TrySendError}; +use std::thread::{self, JoinHandle, Scope}; +use std::time::Duration; + +/// Executor shared by every worker of a pool. The `'static` bound required to +/// move the executor into detached threads is expressed on the pool types +/// instead of this trait so scoped pools can borrow their environment. +pub trait ObjectTaskExecutor: Send + Sync { + fn execute(&self, worker_index: usize, task: T) -> R; +} + +enum ObjectWorkerMessage { + Task(T), + Shutdown, +} + +#[derive(Debug)] +pub enum ObjectWorkerSubmitError { + QueueFull { worker_index: usize, task: T }, + Disconnected { worker_index: usize, task: T }, +} + +pub struct ObjectWorkerPool +where + T: Send + 'static, + R: Send + 'static, + E: ObjectTaskExecutor + 'static, +{ + task_txs: Vec>>, + result_rx: Receiver, + workers: Vec>, + next_worker_idx: usize, + _executor: Arc, +} + +impl ObjectWorkerPool +where + T: Send + 'static, + R: Send + 'static, + E: ObjectTaskExecutor + 'static, +{ + pub fn new(worker_count: usize, queue_capacity: usize, executor: E) -> Result { + if worker_count == 0 { + return Err("ObjectWorkerPool requires at least one worker".to_string()); + } + if queue_capacity == 0 { + return Err("ObjectWorkerPool requires queue_capacity > 0".to_string()); + } + + let executor = Arc::new(executor); + let (result_tx, result_rx) = mpsc::channel::(); + let mut task_txs = Vec::with_capacity(worker_count); + let mut workers = Vec::with_capacity(worker_count); + + for worker_index in 0..worker_count { + let (task_tx, task_rx) = mpsc::sync_channel::>(queue_capacity); + let result_tx = result_tx.clone(); + let executor = Arc::clone(&executor); + let handle = thread::Builder::new() + .name(format!("object-validation-worker-{worker_index}")) + .spawn(move || object_worker_loop(worker_index, task_rx, result_tx, executor)) + .map_err(|e| format!("spawn object worker failed: {e}"))?; + task_txs.push(task_tx); + workers.push(handle); + } + + Ok(Self { + task_txs, + result_rx, + workers, + next_worker_idx: 0, + _executor: executor, + }) + } + + pub fn worker_count(&self) -> usize { + self.task_txs.len() + } + + pub fn next_worker_index(&self) -> usize { + self.next_worker_idx + } + + pub fn try_submit_round_robin(&mut self, task: T) -> Result> { + let worker_index = self.next_worker_idx % self.task_txs.len(); + match self.task_txs[worker_index].try_send(ObjectWorkerMessage::Task(task)) { + Ok(()) => { + self.next_worker_idx = (worker_index + 1) % self.task_txs.len(); + Ok(worker_index) + } + Err(TrySendError::Full(ObjectWorkerMessage::Task(task))) => { + Err(ObjectWorkerSubmitError::QueueFull { worker_index, task }) + } + Err(TrySendError::Disconnected(ObjectWorkerMessage::Task(task))) => { + Err(ObjectWorkerSubmitError::Disconnected { worker_index, task }) + } + Err(TrySendError::Full(ObjectWorkerMessage::Shutdown)) + | Err(TrySendError::Disconnected(ObjectWorkerMessage::Shutdown)) => { + unreachable!("shutdown is never submitted via try_submit_round_robin") + } + } + } + + pub fn recv_result_timeout(&self, timeout: Duration) -> Result, String> { + match self.result_rx.recv_timeout(timeout) { + Ok(result) => Ok(Some(result)), + Err(RecvTimeoutError::Timeout) => Ok(None), + Err(RecvTimeoutError::Disconnected) => { + Err("object worker result channel disconnected".to_string()) + } + } + } + + pub fn shutdown(mut self) -> Result<(), String> { + self.shutdown_inner() + } + + fn shutdown_inner(&mut self) -> Result<(), String> { + if self.workers.is_empty() { + return Ok(()); + } + for tx in &self.task_txs { + tx.send(ObjectWorkerMessage::Shutdown) + .map_err(|e| format!("send shutdown to object worker failed: {e}"))?; + } + let mut first_err = None; + for handle in self.workers.drain(..) { + if let Err(e) = handle.join() + && first_err.is_none() + { + first_err = Some(format!("join object worker failed: {e:?}")); + } + } + if let Some(err) = first_err { + return Err(err); + } + Ok(()) + } +} + +impl Drop for ObjectWorkerPool +where + T: Send + 'static, + R: Send + 'static, + E: ObjectTaskExecutor + 'static, +{ + fn drop(&mut self) { + let _ = self.shutdown_inner(); + } +} + +fn object_worker_loop( + worker_index: usize, + task_rx: Receiver>, + result_tx: mpsc::Sender, + executor: Arc, +) where + T: Send, + R: Send, + E: ObjectTaskExecutor, +{ + while let Ok(ObjectWorkerMessage::Task(task)) = task_rx.recv() { + let result = executor.execute(worker_index, task); + if result_tx.send(result).is_err() { + break; + } + } +} + +/// Scoped variant of `ObjectWorkerPool`: workers are spawned on a +/// `std::thread::Scope`, so tasks, results and the executor may borrow their +/// environment (`'env`) instead of being `'static`. The pool never sends +/// `Shutdown`; workers exit when every task sender is dropped, which happens +/// when the pool itself is dropped ahead of the scope join. +pub struct ScopedObjectWorkerPool<'scope, 'env, T, R, E> +where + T: Send + 'env, + R: Send + 'env, + E: ObjectTaskExecutor + 'env, +{ + task_txs: Vec>>, + result_rx: Receiver, + next_worker_idx: usize, + _executor: Arc, + // Join handles are intentionally not stored: dropping a `ScopedJoinHandle` + // detaches the worker and the enclosing scope joins it on exit, after the + // dropped task senders have made every worker return. + _marker: PhantomData<(&'scope (), &'env ())>, +} + +impl<'scope, 'env, T, R, E> ScopedObjectWorkerPool<'scope, 'env, T, R, E> +where + T: Send + 'env, + R: Send + 'env, + E: ObjectTaskExecutor + 'env, +{ + pub fn new( + scope: &'scope Scope<'scope, 'env>, + worker_count: usize, + queue_capacity: usize, + executor: E, + ) -> Result { + if worker_count == 0 { + return Err("ScopedObjectWorkerPool requires at least one worker".to_string()); + } + if queue_capacity == 0 { + return Err("ScopedObjectWorkerPool requires queue_capacity > 0".to_string()); + } + + let executor = Arc::new(executor); + let (result_tx, result_rx) = mpsc::channel::(); + let mut task_txs = Vec::with_capacity(worker_count); + + for worker_index in 0..worker_count { + let (task_tx, task_rx) = mpsc::sync_channel::>(queue_capacity); + let result_tx = result_tx.clone(); + let executor = Arc::clone(&executor); + thread::Builder::new() + .name(format!("object-validation-worker-{worker_index}")) + .spawn_scoped(scope, move || { + object_worker_loop(worker_index, task_rx, result_tx, executor) + }) + .map_err(|e| format!("spawn scoped object worker failed: {e}"))?; + task_txs.push(task_tx); + } + + Ok(Self { + task_txs, + result_rx, + next_worker_idx: 0, + _executor: executor, + _marker: PhantomData, + }) + } + + pub fn worker_count(&self) -> usize { + self.task_txs.len() + } + + pub fn try_submit_round_robin(&mut self, task: T) -> Result> { + let worker_index = self.next_worker_idx % self.task_txs.len(); + match self.task_txs[worker_index].try_send(ObjectWorkerMessage::Task(task)) { + Ok(()) => { + self.next_worker_idx = (worker_index + 1) % self.task_txs.len(); + Ok(worker_index) + } + Err(TrySendError::Full(ObjectWorkerMessage::Task(task))) => { + Err(ObjectWorkerSubmitError::QueueFull { worker_index, task }) + } + Err(TrySendError::Disconnected(ObjectWorkerMessage::Task(task))) => { + Err(ObjectWorkerSubmitError::Disconnected { worker_index, task }) + } + Err(TrySendError::Full(ObjectWorkerMessage::Shutdown)) + | Err(TrySendError::Disconnected(ObjectWorkerMessage::Shutdown)) => { + unreachable!("shutdown is never submitted via try_submit_round_robin") + } + } + } + + pub fn recv_result_timeout(&self, timeout: Duration) -> Result, String> { + match self.result_rx.recv_timeout(timeout) { + Ok(result) => Ok(Some(result)), + Err(RecvTimeoutError::Timeout) => Ok(None), + Err(RecvTimeoutError::Disconnected) => { + Err("scoped object worker result channel disconnected".to_string()) + } + } + } +} + +impl<'scope, 'env, T, R, E> Drop for ScopedObjectWorkerPool<'scope, 'env, T, R, E> +where + T: Send + 'env, + R: Send + 'env, + E: ObjectTaskExecutor + 'env, +{ + fn drop(&mut self) { + // Close every worker input queue so blocked `recv` calls return and + // the scoped workers exit before the enclosing scope joins them. + self.task_txs.clear(); + } +} + +#[cfg(test)] +mod tests { + use super::{ObjectTaskExecutor, ObjectWorkerPool, ObjectWorkerSubmitError}; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::{Arc, Barrier}; + use std::time::Duration; + + #[derive(Clone)] + struct EchoExecutor; + + impl ObjectTaskExecutor for EchoExecutor { + fn execute(&self, worker_index: usize, task: u32) -> (usize, u32) { + (worker_index, task) + } + } + + #[test] + fn object_worker_pool_rejects_invalid_config_and_shutdowns_explicitly() { + let err = match ObjectWorkerPool::new(0, 1, EchoExecutor) { + Ok(_) => panic!("zero workers should be rejected"), + Err(err) => err, + }; + assert!(err.contains("at least one worker")); + let err = match ObjectWorkerPool::new(1, 0, EchoExecutor) { + Ok(_) => panic!("zero queue should be rejected"), + Err(err) => err, + }; + assert!(err.contains("queue_capacity > 0")); + + let pool = ObjectWorkerPool::new(2, 1, EchoExecutor).expect("pool"); + assert_eq!(pool.worker_count(), 2); + assert_eq!(pool.next_worker_index(), 0); + pool.shutdown().expect("shutdown"); + } + + #[test] + fn object_worker_pool_round_robin_submits_to_worker_queues() { + let mut pool = ObjectWorkerPool::new(3, 4, EchoExecutor).expect("pool"); + assert_eq!(pool.try_submit_round_robin(10).expect("submit 10"), 0); + assert_eq!(pool.try_submit_round_robin(11).expect("submit 11"), 1); + assert_eq!(pool.try_submit_round_robin(12).expect("submit 12"), 2); + assert_eq!(pool.try_submit_round_robin(13).expect("submit 13"), 0); + + let mut results = Vec::new(); + for _ in 0..4 { + results.push( + pool.recv_result_timeout(Duration::from_secs(1)) + .expect("result channel") + .expect("result"), + ); + } + results.sort_by_key(|(_, task)| *task); + assert_eq!(results, vec![(0, 10), (1, 11), (2, 12), (0, 13)]); + } + + struct BlockingExecutor { + barrier: Arc, + started: Arc, + } + + impl ObjectTaskExecutor for BlockingExecutor { + fn execute(&self, _worker_index: usize, task: u32) -> u32 { + self.started.store(true, Ordering::SeqCst); + self.barrier.wait(); + task + } + } + + #[test] + fn object_worker_pool_reports_full_worker_queue_without_advancing_round_robin() { + let barrier = Arc::new(Barrier::new(2)); + let started = Arc::new(AtomicBool::new(false)); + let mut pool = ObjectWorkerPool::new( + 1, + 1, + BlockingExecutor { + barrier: Arc::clone(&barrier), + started: Arc::clone(&started), + }, + ) + .expect("pool"); + + assert_eq!(pool.try_submit_round_robin(1).expect("first task"), 0); + let deadline = std::time::Instant::now() + Duration::from_secs(1); + while !started.load(Ordering::SeqCst) { + assert!( + std::time::Instant::now() < deadline, + "worker did not start first task" + ); + std::thread::sleep(Duration::from_millis(1)); + } + assert_eq!(pool.try_submit_round_robin(2).expect("queued task"), 0); + match pool.try_submit_round_robin(3) { + Err(ObjectWorkerSubmitError::QueueFull { worker_index, task }) => { + assert_eq!(worker_index, 0); + assert_eq!(task, 3); + } + other => panic!("expected queue full, got {other:?}"), + } + assert_eq!(pool.next_worker_index(), 0); + + barrier.wait(); + assert_eq!( + pool.recv_result_timeout(Duration::from_secs(1)) + .expect("result channel"), + Some(1) + ); + barrier.wait(); + assert_eq!( + pool.recv_result_timeout(Duration::from_secs(1)) + .expect("result channel"), + Some(2) + ); + } + + struct BorrowingEchoExecutor<'a> { + base: &'a u32, + } + + impl<'a> ObjectTaskExecutor for BorrowingEchoExecutor<'a> { + fn execute(&self, _worker_index: usize, task: u32) -> u32 { + task + *self.base + } + } + + #[test] + fn scoped_object_worker_pool_borrows_environment_and_processes_tasks() { + let base = 100u32; + std::thread::scope(|scope| { + let mut pool = super::ScopedObjectWorkerPool::new( + scope, + 2, + 2, + BorrowingEchoExecutor { base: &base }, + ) + .expect("scoped pool"); + assert_eq!(pool.worker_count(), 2); + pool.try_submit_round_robin(1).expect("submit 1"); + pool.try_submit_round_robin(2).expect("submit 2"); + let mut results = Vec::new(); + for _ in 0..2 { + results.push( + pool.recv_result_timeout(Duration::from_secs(1)) + .expect("result channel") + .expect("result"), + ); + } + results.sort(); + assert_eq!(results, vec![101, 102]); + // Dropping the pool inside the scope closes the task queues; the + // workers exit on their own and the scope join below must not hang. + drop(pool); + }); + } + + #[test] + fn scoped_object_worker_pool_reports_full_queue() { + std::thread::scope(|scope| { + let barrier = Arc::new(Barrier::new(2)); + let started = Arc::new(AtomicBool::new(false)); + let mut pool = super::ScopedObjectWorkerPool::new( + scope, + 1, + 1, + BlockingExecutor { + barrier: Arc::clone(&barrier), + started: Arc::clone(&started), + }, + ) + .expect("scoped pool"); + pool.try_submit_round_robin(1).expect("first task"); + let deadline = std::time::Instant::now() + Duration::from_secs(1); + while !started.load(Ordering::SeqCst) { + assert!( + std::time::Instant::now() < deadline, + "scoped worker did not start first task" + ); + std::thread::sleep(Duration::from_millis(1)); + } + pool.try_submit_round_robin(2).expect("queued task"); + match pool.try_submit_round_robin(3) { + Err(ObjectWorkerSubmitError::QueueFull { worker_index, task }) => { + assert_eq!(worker_index, 0); + assert_eq!(task, 3); + } + other => panic!("expected queue full, got {other:?}"), + } + // Dropping the pool closes the task queues; releasing the barrier + // afterwards lets the blocked worker finish task 1, observe the + // closed result channel, and exit before the scope join. + drop(pool); + barrier.wait(); + }); + } +} diff --git a/src/scheduler/phase2_scheduler.rs b/src/scheduler/phase2_scheduler.rs new file mode 100644 index 0000000..a625a3e --- /dev/null +++ b/src/scheduler/phase2_scheduler.rs @@ -0,0 +1,332 @@ +use std::collections::{HashMap, VecDeque}; + +use crate::scheduler::types::RepoIdentity; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct CaInstanceId(pub u64); + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct PublicationPointId(pub u64); + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PublicationPointState { + pub ca_instance_id: CaInstanceId, + pub pending_roa_tasks: usize, + pub child_discovery_released: bool, + pub finalized: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Phase2CompletionSnapshot { + pub ca_ready_queue_empty: bool, + pub ca_waiting_repo_empty: bool, + pub repo_tasks_idle: bool, + pub pending_roa_dispatch_empty: bool, + pub worker_queues_empty: bool, + pub object_result_queue_empty: bool, + pub object_workers_idle: bool, + pub inflight_publication_points_empty: bool, +} + +impl Phase2CompletionSnapshot { + pub fn is_complete(&self) -> bool { + self.ca_ready_queue_empty + && self.ca_waiting_repo_empty + && self.repo_tasks_idle + && self.pending_roa_dispatch_empty + && self.worker_queues_empty + && self.object_result_queue_empty + && self.object_workers_idle + && self.inflight_publication_points_empty + } +} + +#[derive(Default)] +pub struct Phase2SchedulerState { + ca_waiting_repo_by_identity: HashMap>, + ca_ready_queue: VecDeque, + inflight_publication_points: HashMap, + pending_roa_dispatch: VecDeque, +} + +impl Phase2SchedulerState { + pub fn new() -> Self { + Self { + ca_waiting_repo_by_identity: HashMap::new(), + ca_ready_queue: VecDeque::new(), + inflight_publication_points: HashMap::new(), + pending_roa_dispatch: VecDeque::new(), + } + } + + pub fn wait_for_repo(&mut self, identity: RepoIdentity, ca_id: CaInstanceId) { + self.ca_waiting_repo_by_identity + .entry(identity) + .or_default() + .push(ca_id); + } + + pub fn release_repo_waiters(&mut self, identity: &RepoIdentity) -> Vec { + let released = self + .ca_waiting_repo_by_identity + .remove(identity) + .unwrap_or_default(); + for ca_id in &released { + self.ca_ready_queue.push_back(*ca_id); + } + released + } + + pub fn push_ready_ca(&mut self, ca_id: CaInstanceId) { + self.ca_ready_queue.push_back(ca_id); + } + + pub fn pop_ready_ca(&mut self) -> Option { + self.ca_ready_queue.pop_front() + } + + pub fn start_publication_point( + &mut self, + pp_id: PublicationPointId, + ca_id: CaInstanceId, + pending_roa_tasks: usize, + ) { + self.inflight_publication_points.insert( + pp_id, + PublicationPointState { + ca_instance_id: ca_id, + pending_roa_tasks, + child_discovery_released: false, + finalized: false, + }, + ); + } + + pub fn mark_child_discovery_released(&mut self, pp_id: PublicationPointId) { + if let Some(state) = self.inflight_publication_points.get_mut(&pp_id) { + state.child_discovery_released = true; + } + } + + pub fn enqueue_roa_task(&mut self, task: T) { + self.pending_roa_dispatch.push_back(task); + } + + pub fn pop_pending_roa_dispatch(&mut self) -> Option { + self.pending_roa_dispatch.pop_front() + } + + pub fn push_front_pending_roa_dispatch(&mut self, task: T) { + self.pending_roa_dispatch.push_front(task); + } + + pub fn record_roa_result(&mut self, pp_id: PublicationPointId) -> Option { + let state = self.inflight_publication_points.get_mut(&pp_id)?; + state.pending_roa_tasks = state.pending_roa_tasks.saturating_sub(1); + if state.pending_roa_tasks == 0 { + state.finalized = true; + self.inflight_publication_points.remove(&pp_id); + Some(pp_id) + } else { + None + } + } + + pub fn waiting_repo_len(&self) -> usize { + self.ca_waiting_repo_by_identity + .values() + .map(Vec::len) + .sum() + } + + pub fn ready_queue_len(&self) -> usize { + self.ca_ready_queue.len() + } + + pub fn inflight_len(&self) -> usize { + self.inflight_publication_points.len() + } + + pub fn pending_roa_dispatch_len(&self) -> usize { + self.pending_roa_dispatch.len() + } + + pub fn publication_point_state( + &self, + pp_id: PublicationPointId, + ) -> Option<&PublicationPointState> { + self.inflight_publication_points.get(&pp_id) + } + + pub fn completion_snapshot( + &self, + repo_tasks_idle: bool, + worker_queues_empty: bool, + object_result_queue_empty: bool, + object_workers_idle: bool, + ) -> Phase2CompletionSnapshot { + Phase2CompletionSnapshot { + ca_ready_queue_empty: self.ca_ready_queue.is_empty(), + ca_waiting_repo_empty: self.ca_waiting_repo_by_identity.is_empty(), + repo_tasks_idle, + pending_roa_dispatch_empty: self.pending_roa_dispatch.is_empty(), + worker_queues_empty, + object_result_queue_empty, + object_workers_idle, + inflight_publication_points_empty: self.inflight_publication_points.is_empty(), + } + } +} + +#[cfg(test)] +mod tests { + use super::{CaInstanceId, Phase2SchedulerState, PublicationPointId}; + use crate::scheduler::object_worker::{ObjectTaskExecutor, ObjectWorkerPool}; + use crate::scheduler::types::RepoIdentity; + use std::time::Duration; + + fn identity(name: &str) -> RepoIdentity { + RepoIdentity::new( + Some(format!("https://example.test/{name}/notification.xml")), + format!("rsync://example.test/{name}/"), + ) + } + + #[test] + fn scheduler_repo_ready_moves_waiting_ca_to_ready_queue() { + let mut state = Phase2SchedulerState::::new(); + let repo = identity("arin"); + state.wait_for_repo(repo.clone(), CaInstanceId(1)); + state.wait_for_repo(repo.clone(), CaInstanceId(2)); + assert_eq!(state.waiting_repo_len(), 2); + + let released = state.release_repo_waiters(&repo); + assert_eq!(released, vec![CaInstanceId(1), CaInstanceId(2)]); + assert_eq!(state.waiting_repo_len(), 0); + assert_eq!(state.ready_queue_len(), 2); + assert_eq!(state.pop_ready_ca(), Some(CaInstanceId(1))); + assert_eq!(state.pop_ready_ca(), Some(CaInstanceId(2))); + } + + #[test] + fn scheduler_releases_child_before_roa_results_finalize_parent() { + let mut state = Phase2SchedulerState::::new(); + let pp = PublicationPointId(10); + state.start_publication_point(pp, CaInstanceId(1), 2); + state.mark_child_discovery_released(pp); + state.push_ready_ca(CaInstanceId(2)); + + let pp_state = state.publication_point_state(pp).expect("inflight pp"); + assert!(pp_state.child_discovery_released); + assert_eq!(pp_state.pending_roa_tasks, 2); + assert_eq!(state.pop_ready_ca(), Some(CaInstanceId(2))); + assert_eq!(state.record_roa_result(pp), None); + assert_eq!(state.inflight_len(), 1); + assert_eq!(state.record_roa_result(pp), Some(pp)); + assert_eq!(state.inflight_len(), 0); + } + + #[test] + fn scheduler_completion_requires_all_queues_and_inflight_to_be_empty() { + let mut state = Phase2SchedulerState::new(); + assert!( + state + .completion_snapshot(true, true, true, true) + .is_complete() + ); + + state.enqueue_roa_task(1u64); + assert!( + !state + .completion_snapshot(true, true, true, true) + .is_complete() + ); + assert_eq!(state.pop_pending_roa_dispatch(), Some(1)); + assert!( + state + .completion_snapshot(true, true, true, true) + .is_complete() + ); + } + + #[test] + fn scheduler_can_retry_pending_roa_task_at_front() { + let mut state = Phase2SchedulerState::new(); + state.enqueue_roa_task(1u64); + state.enqueue_roa_task(2u64); + + assert_eq!(state.pop_pending_roa_dispatch(), Some(1)); + state.push_front_pending_roa_dispatch(3); + assert_eq!(state.pop_pending_roa_dispatch(), Some(3)); + assert_eq!(state.pop_pending_roa_dispatch(), Some(2)); + assert_eq!(state.pop_pending_roa_dispatch(), None); + } + + #[derive(Clone, Debug, PartialEq, Eq)] + struct TestRoaTask { + pp_id: PublicationPointId, + value: u64, + } + + #[derive(Clone, Debug, PartialEq, Eq)] + struct TestRoaResult { + worker_index: usize, + pp_id: PublicationPointId, + value: u64, + } + + #[derive(Clone)] + struct TestRoaExecutor; + + impl ObjectTaskExecutor for TestRoaExecutor { + fn execute(&self, worker_index: usize, task: TestRoaTask) -> TestRoaResult { + TestRoaResult { + worker_index, + pp_id: task.pp_id, + value: task.value, + } + } + } + + #[test] + fn scheduler_dispatches_pending_roa_tasks_to_workers_and_finalizes_on_results() { + let pp = PublicationPointId(42); + let mut state = Phase2SchedulerState::new(); + state.start_publication_point(pp, CaInstanceId(7), 3); + state.mark_child_discovery_released(pp); + for value in 0..3 { + state.enqueue_roa_task(TestRoaTask { pp_id: pp, value }); + } + + let mut pool = ObjectWorkerPool::new(2, 4, TestRoaExecutor).expect("object pool"); + while let Some(task) = state.pop_pending_roa_dispatch() { + pool.try_submit_round_robin(task).expect("submit task"); + } + assert_eq!(state.pending_roa_dispatch_len(), 0); + + let mut results = Vec::new(); + for _ in 0..3 { + let result = pool + .recv_result_timeout(Duration::from_secs(1)) + .expect("result channel") + .expect("result"); + let finalized = state.record_roa_result(result.pp_id); + results.push(result); + if results.len() < 3 { + assert_eq!(finalized, None); + } else { + assert_eq!(finalized, Some(pp)); + } + } + results.sort_by_key(|result| result.value); + assert_eq!(results[0].worker_index, 0); + assert_eq!(results[1].worker_index, 1); + assert_eq!(results[2].worker_index, 0); + assert_eq!(state.inflight_len(), 0); + assert!( + state + .completion_snapshot(true, true, true, true) + .is_complete() + ); + } +} diff --git a/src/scheduler/repo_runtime.rs b/src/scheduler/repo_runtime.rs new file mode 100644 index 0000000..c3616bd --- /dev/null +++ b/src/scheduler/repo_runtime.rs @@ -0,0 +1,21 @@ +#![allow(clippy::type_complexity)] + +use std::collections::HashSet; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use crate::output::report::Warning; +use crate::scheduler::repo_scheduler::TransportRequestAction; +use crate::scheduler::repo_worker::{RepoTransportExecutor, RepoTransportWorkerPool}; +use crate::scheduler::run_coordinator::GlobalRunCoordinator; +use crate::scheduler::types::{ + RepoIdentity, RepoRequester, RepoRuntimeState, RepoTransportMode, RepoTransportResultEnvelope, + RepoTransportResultKind, +}; +use crate::validation::policy::SyncPreference; +use crate::validation::tree::CaInstanceHandle; + +include!("repo_runtime/types_and_trait.rs"); +include!("repo_runtime/phase1_runtime.rs"); +include!("repo_runtime/runtime_trait_impl.rs"); +include!("repo_runtime/outcome.rs"); diff --git a/src/scheduler/repo_runtime/outcome.rs b/src/scheduler/repo_runtime/outcome.rs new file mode 100644 index 0000000..a34f6d0 --- /dev/null +++ b/src/scheduler/repo_runtime/outcome.rs @@ -0,0 +1,58 @@ +// Conversion from transport envelopes to runtime outcomes. + +fn outcome_from_transport_result( + envelope: &RepoTransportResultEnvelope, + state: RepoRuntimeState, +) -> RepoSyncRuntimeOutcome { + match (&envelope.result, state) { + (RepoTransportResultKind::Success { source, warnings }, RepoRuntimeState::RrdpOk) => { + RepoSyncRuntimeOutcome { + repo_sync_ok: true, + repo_sync_err: None, + repo_sync_source: Some(source.clone()), + repo_sync_phase: Some("rrdp_ok".to_string()), + repo_sync_duration_ms: envelope.timing_ms, + warnings: warnings.clone(), + } + } + (RepoTransportResultKind::Success { source, warnings }, RepoRuntimeState::RsyncOk) => { + RepoSyncRuntimeOutcome { + repo_sync_ok: true, + repo_sync_err: None, + repo_sync_source: Some(source.clone()), + repo_sync_phase: Some(if envelope.repo_identity.notification_uri.is_some() { + "rrdp_failed_rsync_ok".to_string() + } else { + "rsync_only_ok".to_string() + }), + repo_sync_duration_ms: envelope.timing_ms, + warnings: warnings.clone(), + } + } + ( + RepoTransportResultKind::Failed { + detail, warnings, .. + }, + RepoRuntimeState::FailedTerminal, + ) => RepoSyncRuntimeOutcome { + repo_sync_ok: false, + repo_sync_err: Some(detail.clone()), + repo_sync_source: None, + repo_sync_phase: Some(if envelope.repo_identity.notification_uri.is_some() { + "rrdp_failed_rsync_failed".to_string() + } else { + "rsync_failed".to_string() + }), + repo_sync_duration_ms: envelope.timing_ms, + warnings: warnings.clone(), + }, + _ => RepoSyncRuntimeOutcome { + repo_sync_ok: false, + repo_sync_err: Some("repo runtime state unresolved".to_string()), + repo_sync_source: None, + repo_sync_phase: Some("repo_runtime_unresolved".to_string()), + repo_sync_duration_ms: envelope.timing_ms, + warnings: Vec::new(), + }, + } +} diff --git a/src/scheduler/repo_runtime/phase1_runtime.rs b/src/scheduler/repo_runtime/phase1_runtime.rs new file mode 100644 index 0000000..9057b41 --- /dev/null +++ b/src/scheduler/repo_runtime/phase1_runtime.rs @@ -0,0 +1,329 @@ +// Phase-one runtime request registration and result pumping. + +pub struct Phase1RepoSyncRuntime { + coordinator: Mutex, + worker_pool: Mutex>, + retry_short_rsync_scopes: Mutex>, + rsync_scope_resolver: Arc String + Send + Sync>, + rsync_failure_scope_resolver: Arc Option + Send + Sync>, + sync_preference: SyncPreference, +} + +impl Phase1RepoSyncRuntime { + pub fn new( + coordinator: GlobalRunCoordinator, + worker_pool: RepoTransportWorkerPool, + rsync_scope_resolver: Arc String + Send + Sync>, + sync_preference: SyncPreference, + ) -> Self { + Self::new_with_failure_scope( + coordinator, + worker_pool, + rsync_scope_resolver, + Arc::new(|_base: &str| None), + sync_preference, + ) + } + + pub fn new_with_failure_scope( + coordinator: GlobalRunCoordinator, + worker_pool: RepoTransportWorkerPool, + rsync_scope_resolver: Arc String + Send + Sync>, + rsync_failure_scope_resolver: Arc Option + Send + Sync>, + sync_preference: SyncPreference, + ) -> Self { + Self { + coordinator: Mutex::new(coordinator), + worker_pool: Mutex::new(worker_pool), + retry_short_rsync_scopes: Mutex::new(HashSet::new()), + rsync_scope_resolver, + rsync_failure_scope_resolver, + sync_preference, + } + } + + fn build_requester(ca: &CaInstanceHandle) -> RepoRequester { + RepoRequester { + tal_id: ca.tal_id.clone(), + rir_id: ca.tal_id.clone(), + parent_node_id: None, + ca_instance_handle_id: format!("{}:{}", ca.tal_id, ca.manifest_rsync_uri), + publication_point_rsync_uri: ca.publication_point_rsync_uri.clone(), + manifest_rsync_uri: ca.manifest_rsync_uri.clone(), + } + } + + fn build_identity(ca: &CaInstanceHandle) -> RepoIdentity { + RepoIdentity::new(ca.rrdp_notification_uri.clone(), ca.rsync_base_uri.clone()) + } + + fn request_transport_for_ca( + &self, + ca: &CaInstanceHandle, + priority: u8, + ) -> Result { + let identity = Self::build_identity(ca); + let requester = Self::build_requester(ca); + let rsync_scope_uri = (self.rsync_scope_resolver)(&identity.rsync_base_uri); + let rsync_failure_scope_uri = (self.rsync_failure_scope_resolver)(&identity.rsync_base_uri); + let action = { + let mut coordinator = self.coordinator.lock().expect("coordinator lock poisoned"); + coordinator.register_transport_request( + identity.clone(), + requester, + time::OffsetDateTime::now_utc(), + priority, + rsync_scope_uri, + rsync_failure_scope_uri, + self.sync_preference, + false, + ) + }; + + match action { + TransportRequestAction::Enqueue(task) => { + crate::logging::progress::emit!( + "phase1_repo_task_enqueued", + serde_json::json!({ + "manifest_rsync_uri": ca.manifest_rsync_uri, + "publication_point_rsync_uri": ca.publication_point_rsync_uri, + "repo_key_rsync_base_uri": task.repo_identity.rsync_base_uri, + "rsync_failure_scope_uri": task.rsync_failure_scope_uri, + "repo_key_notification_uri": task.repo_identity.notification_uri, + "priority": priority, + "transport_mode": match task.mode { + RepoTransportMode::Rrdp => "rrdp", + RepoTransportMode::Rsync => "rsync", + }, + }), + ); + self.drain_pending_transport_tasks()?; + Ok(RepoSyncRequestStatus::Pending { + identity, + state: self + .runtime_state_for_identity(&task.repo_identity) + .unwrap_or(RepoRuntimeState::WaitingRrdp), + }) + } + TransportRequestAction::Waiting { state } => { + crate::logging::progress::emit!( + "phase1_repo_task_waiting", + serde_json::json!({ + "manifest_rsync_uri": ca.manifest_rsync_uri, + "publication_point_rsync_uri": ca.publication_point_rsync_uri, + "repo_key_rsync_base_uri": identity.rsync_base_uri, + "rsync_failure_scope_uri": (self.rsync_failure_scope_resolver)(&identity.rsync_base_uri), + "repo_key_notification_uri": identity.notification_uri, + "priority": priority, + "runtime_state": format!("{state:?}"), + }), + ); + Ok(RepoSyncRequestStatus::Pending { identity, state }) + } + TransportRequestAction::ReusedSuccess(result) + | TransportRequestAction::ReusedTerminalFailure(result) => { + crate::logging::progress::emit!( + "phase1_repo_task_reused", + serde_json::json!({ + "manifest_rsync_uri": ca.manifest_rsync_uri, + "publication_point_rsync_uri": ca.publication_point_rsync_uri, + "repo_key_rsync_base_uri": identity.rsync_base_uri, + "rsync_failure_scope_uri": result.rsync_failure_scope_uri, + "repo_key_notification_uri": identity.notification_uri, + "priority": priority, + "transport_mode": match result.mode { + RepoTransportMode::Rrdp => "rrdp", + RepoTransportMode::Rsync => "rsync", + }, + }), + ); + Ok(RepoSyncRequestStatus::Ready { + outcome: outcome_from_transport_result( + &result, + self.runtime_state_for_identity(&identity) + .unwrap_or(RepoRuntimeState::Init), + ), + identity, + }) + } + } + } + + fn drain_pending_transport_tasks(&self) -> Result<(), String> { + loop { + let maybe_task = { + let mut coordinator = self.coordinator.lock().expect("coordinator lock poisoned"); + coordinator.pop_next_transport_task() + }; + let Some(task) = maybe_task else { + break; + }; + { + let mut coordinator = self.coordinator.lock().expect("coordinator lock poisoned"); + coordinator + .mark_transport_running(&task.dedup_key, time::OffsetDateTime::now_utc())?; + } + crate::logging::progress::emit!( + "phase1_repo_task_dispatched", + serde_json::json!({ + "repo_key_rsync_base_uri": task.repo_identity.rsync_base_uri, + "rsync_failure_scope_uri": task.rsync_failure_scope_uri, + "repo_key_notification_uri": task.repo_identity.notification_uri, + "requester_count": task.requesters.len(), + "priority": task.priority, + "transport_mode": match task.mode { + RepoTransportMode::Rrdp => "rrdp", + RepoTransportMode::Rsync => "rsync", + }, + }), + ); + let pool = self.worker_pool.lock().expect("worker pool lock poisoned"); + pool.submit(task)?; + } + Ok(()) + } + + fn pump_one_transport_result( + &self, + timeout: Duration, + ) -> Result, String> { + let envelope = { + let pool = self.worker_pool.lock().expect("worker pool lock poisoned"); + pool.recv_result_timeout(timeout)? + }; + let Some(envelope) = envelope else { + return Ok(None); + }; + let transport_identity = envelope.repo_identity.clone(); + let completed_envelope = envelope.clone(); + crate::logging::progress::emit!( + "phase1_repo_task_result", + serde_json::json!({ + "repo_key_rsync_base_uri": envelope.repo_identity.rsync_base_uri, + "rsync_failure_scope_uri": envelope.rsync_failure_scope_uri, + "repo_key_notification_uri": envelope.repo_identity.notification_uri, + "timing_ms": envelope.timing_ms, + "transport_mode": match envelope.mode { + RepoTransportMode::Rrdp => "rrdp", + RepoTransportMode::Rsync => "rsync", + }, + "result": match &envelope.result { + RepoTransportResultKind::Success { .. } => "success", + RepoTransportResultKind::Failed { .. } => "failed", + }, + }), + ); + let finished_at = time::OffsetDateTime::now_utc(); + let completion = { + let mut coordinator = self.coordinator.lock().expect("coordinator lock poisoned"); + coordinator.complete_transport_result(envelope, finished_at)? + }; + if !completion.follow_up_tasks.is_empty() { + let mut coordinator = self.coordinator.lock().expect("coordinator lock poisoned"); + for mut task in completion.follow_up_tasks { + if let crate::scheduler::types::RepoDedupKey::RsyncScope { rsync_scope_uri } = + &task.dedup_key + && self + .retry_short_rsync_scopes + .lock() + .expect("retry short rsync scopes lock poisoned") + .contains(rsync_scope_uri) + { + task.retry_short_timeout = true; + } + crate::logging::progress::emit!( + "phase1_repo_task_enqueued", + serde_json::json!({ + "manifest_rsync_uri": serde_json::Value::Null, + "publication_point_rsync_uri": task.requesters.first().map(|r| r.publication_point_rsync_uri.clone()), + "repo_key_rsync_base_uri": task.repo_identity.rsync_base_uri, + "repo_key_notification_uri": task.repo_identity.notification_uri, + "priority": task.priority, + "transport_mode": "rsync", + }), + ); + coordinator.push_transport_task(task); + } + } + self.drain_pending_transport_tasks()?; + let completions = { + let coordinator = self.coordinator.lock().expect("coordinator lock poisoned"); + coordinator + .finalized_runtime_records_for_transport_result(&completed_envelope) + .into_iter() + .filter_map(|record| { + let outcome = match record.state { + RepoRuntimeState::RrdpOk | RepoRuntimeState::RsyncOk => record + .last_success + .as_ref() + .map(|result| outcome_from_transport_result(result, record.state)), + RepoRuntimeState::FailedTerminal => record + .terminal_failure + .as_ref() + .map(|result| outcome_from_transport_result(result, record.state)), + _ => None, + }?; + Some(RepoSyncRuntimeCompletion { + identity: record.identity, + state: record.state, + outcome, + }) + }) + .collect::>() + }; + if completions.is_empty() { + return Ok(None); + } + Ok(Some(RepoSyncRuntimeEvent { + transport_identity, + completions, + })) + } + + fn pump_transport_results( + &self, + timeout: Duration, + max_events: usize, + ) -> Result, String> { + let max_events = max_events.max(1); + let mut events = Vec::new(); + for index in 0..max_events { + let poll_timeout = if index == 0 { + timeout + } else { + Duration::from_millis(0) + }; + let Some(event) = self.pump_one_transport_result(poll_timeout)? else { + break; + }; + events.push(event); + } + Ok(events) + } + + fn runtime_state_for_identity(&self, identity: &RepoIdentity) -> Option { + let coordinator = self.coordinator.lock().expect("coordinator lock poisoned"); + coordinator + .runtime_record(identity) + .map(|record| record.state) + } + + fn resolved_outcome_for_identity( + &self, + identity: &RepoIdentity, + ) -> Option { + let coordinator = self.coordinator.lock().expect("coordinator lock poisoned"); + let record = coordinator.runtime_record(identity)?; + match record.state { + RepoRuntimeState::RrdpOk | RepoRuntimeState::RsyncOk => record + .last_success + .as_ref() + .map(|result| outcome_from_transport_result(result, record.state)), + RepoRuntimeState::FailedTerminal => record + .terminal_failure + .as_ref() + .map(|result| outcome_from_transport_result(result, record.state)), + _ => None, + } + } +} diff --git a/src/scheduler/repo_runtime/runtime_trait_impl.rs b/src/scheduler/repo_runtime/runtime_trait_impl.rs new file mode 100644 index 0000000..a2de18f --- /dev/null +++ b/src/scheduler/repo_runtime/runtime_trait_impl.rs @@ -0,0 +1,68 @@ +// RepoSyncRuntime implementation for the phase-one runtime. + +impl RepoSyncRuntime for Phase1RepoSyncRuntime { + fn sync_publication_point_repo( + &self, + ca: &CaInstanceHandle, + ) -> Result { + if let RepoSyncRequestStatus::Ready { outcome, .. } = + self.request_publication_point_repo(ca, 0)? + { + return Ok(outcome); + } + let identity = Self::build_identity(ca); + loop { + if let Some(done) = self.resolved_outcome_for_identity(&identity) { + return Ok(done); + } + let _ = self.recv_repo_result_timeout(Duration::from_millis(50))?; + } + } + + fn request_publication_point_repo( + &self, + ca: &CaInstanceHandle, + priority: u8, + ) -> Result { + self.request_transport_for_ca(ca, priority) + } + + fn recv_repo_result_timeout( + &self, + timeout: Duration, + ) -> Result, String> { + self.pump_one_transport_result(timeout) + } + + fn drain_repo_results_timeout( + &self, + timeout: Duration, + max_events: usize, + ) -> Result, String> { + self.pump_transport_results(timeout, max_events) + } + + fn reset_run_state(&self) -> Result<(), String> { + { + let mut coordinator = self.coordinator.lock().expect("coordinator lock poisoned"); + if coordinator.stats.repo_tasks_running != 0 { + return Err(format!( + "cannot reset repo runtime with {} repo task(s) still running", + coordinator.stats.repo_tasks_running + )); + } + coordinator.reset_run_state(); + } + loop { + let maybe_result = { + let pool = self.worker_pool.lock().expect("worker pool lock poisoned"); + pool.recv_result_timeout(Duration::from_millis(0))? + }; + if maybe_result.is_none() { + break; + } + } + Ok(()) + } + +} diff --git a/src/scheduler/repo_runtime/types_and_trait.rs b/src/scheduler/repo_runtime/types_and_trait.rs new file mode 100644 index 0000000..54d1a05 --- /dev/null +++ b/src/scheduler/repo_runtime/types_and_trait.rs @@ -0,0 +1,78 @@ +// Repository runtime outcomes, events, and runtime trait. + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RepoSyncRuntimeOutcome { + pub repo_sync_ok: bool, + pub repo_sync_err: Option, + pub repo_sync_source: Option, + pub repo_sync_phase: Option, + pub repo_sync_duration_ms: u64, + pub warnings: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RepoSyncRequestStatus { + Ready { + identity: RepoIdentity, + outcome: RepoSyncRuntimeOutcome, + }, + Pending { + identity: RepoIdentity, + state: RepoRuntimeState, + }, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RepoSyncRuntimeCompletion { + pub identity: RepoIdentity, + pub state: RepoRuntimeState, + pub outcome: RepoSyncRuntimeOutcome, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RepoSyncRuntimeEvent { + pub transport_identity: RepoIdentity, + pub completions: Vec, +} + +pub trait RepoSyncRuntime: Send + Sync { + fn sync_publication_point_repo( + &self, + ca: &CaInstanceHandle, + ) -> Result; + + fn request_publication_point_repo( + &self, + ca: &CaInstanceHandle, + priority: u8, + ) -> Result; + + fn recv_repo_result_timeout( + &self, + timeout: Duration, + ) -> Result, String>; + + fn drain_repo_results_timeout( + &self, + timeout: Duration, + max_events: usize, + ) -> Result, String> { + let max_events = max_events.max(1); + let mut events = Vec::new(); + for index in 0..max_events { + let poll_timeout = if index == 0 { + timeout + } else { + Duration::from_millis(0) + }; + let Some(event) = self.recv_repo_result_timeout(poll_timeout)? else { + break; + }; + events.push(event); + } + Ok(events) + } + + fn reset_run_state(&self) -> Result<(), String>; + +} diff --git a/src/scheduler/repo_scheduler.rs b/src/scheduler/repo_scheduler.rs new file mode 100644 index 0000000..a31099b --- /dev/null +++ b/src/scheduler/repo_scheduler.rs @@ -0,0 +1,84 @@ +#![allow(clippy::too_many_arguments)] + +use std::collections::{HashMap, HashSet}; + +use crate::scheduler::types::{ + InFlightRepoEntry, RepoDedupKey, RepoIdentity, RepoKey, RepoRequester, RepoRuntimeState, + RepoSyncResultEnvelope, RepoSyncResultKind, RepoSyncResultRef, RepoSyncTask, RepoTaskState, + RepoTransportMode, RepoTransportResultEnvelope, RepoTransportResultKind, RepoTransportTask, +}; +use crate::validation::policy::SyncPreference; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RepoRequestAction { + Enqueued(RepoSyncTask), + Waiting, + Reused(RepoSyncResultRef), + FailedReuse { detail: String }, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RepoCompletion { + pub repo_key: RepoKey, + pub released_requesters: Vec, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum TransportTaskState { + Pending, + Running, + Finished, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TransportInFlightEntry { + pub state: TransportTaskState, + pub task: RepoTransportTask, + pub waiting_requesters: Vec, + pub last_result: Option, + pub started_at: Option, + pub finished_at: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RepoRuntimeRecord { + pub identity: RepoIdentity, + pub state: RepoRuntimeState, + pub rrdp_notification_key: Option, + pub rsync_scope_key: String, + pub rsync_failure_scope_key: Option, + pub requesters: Vec, + pub validation_time: time::OffsetDateTime, + pub priority: u8, + pub last_success: Option, + pub terminal_failure: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum TransportRequestAction { + Enqueue(RepoTransportTask), + Waiting { state: RepoRuntimeState }, + ReusedSuccess(RepoTransportResultEnvelope), + ReusedTerminalFailure(RepoTransportResultEnvelope), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TransportCompletion { + pub released_requesters: Vec, + pub follow_up_tasks: Vec, +} + +#[derive(Default)] +pub struct TransportStateTables { + rrdp_inflight: HashMap, + rsync_inflight: HashMap, + rsync_failure_by_scope: HashMap, + rsync_failure_probe_inflight: HashMap, + rsync_failure_scope_reachable: HashSet, + runtime_records: HashMap, +} + +include!("repo_scheduler/transport_state.rs"); +include!("repo_scheduler/repo_state.rs"); +include!("repo_scheduler/tests_repo.rs"); +include!("repo_scheduler/tests_transport.rs"); diff --git a/src/scheduler/repo_scheduler/repo_state.rs b/src/scheduler/repo_scheduler/repo_state.rs new file mode 100644 index 0000000..6e45f2c --- /dev/null +++ b/src/scheduler/repo_scheduler/repo_state.rs @@ -0,0 +1,199 @@ +// Reusable transport failures and repository in-flight state. + +fn reusable_rsync_failure_scope(result: &RepoTransportResultEnvelope) -> Option<&str> { + let scope = result.rsync_failure_scope_uri.as_deref()?; + match (&result.mode, &result.result) { + (RepoTransportMode::Rsync, RepoTransportResultKind::Failed { detail, .. }) + if is_host_level_rsync_failure(detail) => + { + Some(scope) + } + _ => None, + } +} + +pub(crate) fn is_host_level_rsync_failure(detail: &str) -> bool { + let lower = detail.to_ascii_lowercase(); + lower.contains("timeout waiting for daemon connection") + || lower.contains("failed to connect") + || lower.contains("no route to host") + || lower.contains("network is unreachable") + || lower.contains("connection refused") + || lower.contains("name or service not known") + || lower.contains("temporary failure in name resolution") +} + +#[derive(Default)] +pub struct InFlightRepoTable { + entries: HashMap, +} + +impl InFlightRepoTable { + pub fn new() -> Self { + Self::default() + } + + pub fn len(&self) -> usize { + self.entries.len() + } + + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } + + pub fn reset_run_state(&mut self) { + self.entries.clear(); + } + + pub fn get(&self, key: &RepoKey) -> Option<&InFlightRepoEntry> { + self.entries.get(key) + } + + pub fn last_result(&self, key: &RepoKey) -> Option<&RepoSyncResultEnvelope> { + self.entries + .get(key) + .and_then(|entry| entry.last_result.as_ref()) + } + + pub fn register_request( + &mut self, + repo_key: RepoKey, + requester: RepoRequester, + validation_time: time::OffsetDateTime, + sync_preference: SyncPreference, + priority: u8, + ) -> RepoRequestAction { + match self.entries.get_mut(&repo_key) { + Some(entry) => match entry.state { + RepoTaskState::Pending | RepoTaskState::Running => { + entry.waiting_requesters.push(requester); + RepoRequestAction::Waiting + } + RepoTaskState::Succeeded | RepoTaskState::Reused => { + RepoRequestAction::Reused(entry.result_ref.clone().expect("result_ref exists")) + } + RepoTaskState::Failed => RepoRequestAction::FailedReuse { + detail: entry + .last_error + .clone() + .unwrap_or_else(|| "repo sync failed".to_string()), + }, + }, + None => { + let task = RepoSyncTask { + repo_key: repo_key.clone(), + validation_time, + sync_preference, + tal_id: requester.tal_id.clone(), + rir_id: requester.rir_id.clone(), + priority, + requesters: vec![requester], + }; + self.entries.insert( + repo_key, + InFlightRepoEntry { + state: RepoTaskState::Pending, + task_ref: Some(task.clone()), + waiting_requesters: Vec::new(), + result_ref: None, + last_result: None, + last_error: None, + started_at: None, + finished_at: None, + }, + ); + RepoRequestAction::Enqueued(task) + } + } + } + + pub fn mark_running( + &mut self, + repo_key: &RepoKey, + started_at: time::OffsetDateTime, + ) -> Result<(), String> { + let entry = self + .entries + .get_mut(repo_key) + .ok_or_else(|| format!("repo not found: {}", repo_key.rsync_base_uri))?; + match entry.state { + RepoTaskState::Pending => { + entry.state = RepoTaskState::Running; + entry.started_at = Some(started_at); + Ok(()) + } + other => Err(format!("repo cannot transition to running from {other:?}")), + } + } + + pub fn complete_success( + &mut self, + repo_key: &RepoKey, + result: RepoSyncResultEnvelope, + finished_at: time::OffsetDateTime, + ) -> Result { + let entry = self + .entries + .get_mut(repo_key) + .ok_or_else(|| format!("repo not found: {}", repo_key.rsync_base_uri))?; + match entry.state { + RepoTaskState::Pending | RepoTaskState::Running => { + let result_ref = match &result.result { + RepoSyncResultKind::Success(result_ref) + | RepoSyncResultKind::Reused(result_ref) => result_ref.clone(), + RepoSyncResultKind::Failed { detail } => { + return Err(format!( + "success completion called with failure result: {detail}" + )); + } + }; + entry.state = RepoTaskState::Succeeded; + entry.result_ref = Some(result_ref); + entry.last_result = Some(result); + entry.last_error = None; + entry.finished_at = Some(finished_at); + entry.task_ref = None; + let released_requesters = std::mem::take(&mut entry.waiting_requesters); + Ok(RepoCompletion { + repo_key: repo_key.clone(), + released_requesters, + }) + } + other => Err(format!("repo cannot transition to success from {other:?}")), + } + } + + pub fn complete_failure( + &mut self, + repo_key: &RepoKey, + result: RepoSyncResultEnvelope, + finished_at: time::OffsetDateTime, + ) -> Result { + let entry = self + .entries + .get_mut(repo_key) + .ok_or_else(|| format!("repo not found: {}", repo_key.rsync_base_uri))?; + match entry.state { + RepoTaskState::Pending | RepoTaskState::Running => { + let detail = match &result.result { + RepoSyncResultKind::Failed { detail } => detail.clone(), + RepoSyncResultKind::Success(_) | RepoSyncResultKind::Reused(_) => { + return Err("failure completion called with success result".to_string()); + } + }; + entry.state = RepoTaskState::Failed; + entry.result_ref = None; + entry.last_result = Some(result); + entry.last_error = Some(detail); + entry.finished_at = Some(finished_at); + entry.task_ref = None; + let released_requesters = std::mem::take(&mut entry.waiting_requesters); + Ok(RepoCompletion { + repo_key: repo_key.clone(), + released_requesters, + }) + } + other => Err(format!("repo cannot transition to failure from {other:?}")), + } + } +} diff --git a/src/scheduler/repo_scheduler/tests_repo.rs b/src/scheduler/repo_scheduler/tests_repo.rs new file mode 100644 index 0000000..02f6fab --- /dev/null +++ b/src/scheduler/repo_scheduler/tests_repo.rs @@ -0,0 +1,231 @@ +// Repository scheduler state transition tests. + +#[cfg(test)] +mod tests { + use crate::scheduler::repo_scheduler::{InFlightRepoTable, RepoRequestAction}; + use crate::scheduler::types::{ + RepoKey, RepoRequester, RepoSyncResultEnvelope, RepoSyncResultKind, RepoSyncResultRef, + RepoTaskState, + }; + use crate::validation::policy::SyncPreference; + + fn requester(tal_id: &str, rir_id: &str, manifest: &str) -> RepoRequester { + RepoRequester { + tal_id: tal_id.to_string(), + rir_id: rir_id.to_string(), + parent_node_id: None, + ca_instance_handle_id: format!("{tal_id}:{manifest}"), + publication_point_rsync_uri: "rsync://example.test/repo/".to_string(), + manifest_rsync_uri: manifest.to_string(), + } + } + + #[test] + fn inflight_repo_table_stores_entries_by_repo_key() { + let key = RepoKey::new("rsync://example.test/repo/", None); + let mut table = InFlightRepoTable::new(); + let action = table.register_request( + key.clone(), + requester("arin", "arin", "rsync://example.test/repo/root.mft"), + time::OffsetDateTime::UNIX_EPOCH, + SyncPreference::RrdpThenRsync, + 0, + ); + assert!(matches!(action, RepoRequestAction::Enqueued(_))); + assert_eq!(table.len(), 1); + assert_eq!( + table.get(&key).map(|entry| entry.state), + Some(RepoTaskState::Pending) + ); + } + + #[test] + fn running_repo_request_is_merged_into_waiting_list() { + let key = RepoKey::new("rsync://example.test/repo/", None); + let mut table = InFlightRepoTable::new(); + let _ = table.register_request( + key.clone(), + requester("arin", "arin", "rsync://example.test/repo/root.mft"), + time::OffsetDateTime::UNIX_EPOCH, + SyncPreference::RrdpThenRsync, + 0, + ); + table + .mark_running(&key, time::OffsetDateTime::UNIX_EPOCH) + .expect("mark running"); + + let action = table.register_request( + key.clone(), + requester("apnic", "apnic", "rsync://example.test/repo/other.mft"), + time::OffsetDateTime::UNIX_EPOCH, + SyncPreference::RrdpThenRsync, + 0, + ); + assert_eq!(action, RepoRequestAction::Waiting); + assert_eq!(table.get(&key).unwrap().waiting_requesters.len(), 1); + } + + #[test] + fn complete_success_releases_waiting_requesters_and_reuses_result() { + let key = RepoKey::new("rsync://example.test/repo/", None); + let mut table = InFlightRepoTable::new(); + let _ = table.register_request( + key.clone(), + requester("arin", "arin", "rsync://example.test/repo/root.mft"), + time::OffsetDateTime::UNIX_EPOCH, + SyncPreference::RrdpThenRsync, + 0, + ); + table + .mark_running(&key, time::OffsetDateTime::UNIX_EPOCH) + .expect("mark running"); + let waiting = requester("ripe", "ripe", "rsync://example.test/repo/child.mft"); + let _ = table.register_request( + key.clone(), + waiting.clone(), + time::OffsetDateTime::UNIX_EPOCH, + SyncPreference::RrdpThenRsync, + 0, + ); + + let result_ref = RepoSyncResultRef { + repo_key: key.clone(), + source: "rrdp".to_string(), + }; + let completion = table + .complete_success( + &key, + RepoSyncResultEnvelope { + repo_key: key.clone(), + tal_id: "arin".to_string(), + rir_id: "arin".to_string(), + result: RepoSyncResultKind::Success(result_ref.clone()), + phase: Some("rrdp_ok".to_string()), + timing_ms: 10, + warnings: Vec::new(), + }, + time::OffsetDateTime::UNIX_EPOCH, + ) + .expect("complete success"); + assert_eq!(completion.released_requesters, vec![waiting]); + assert_eq!(table.get(&key).unwrap().state, RepoTaskState::Succeeded); + + let action = table.register_request( + key, + requester("afrinic", "afrinic", "rsync://example.test/repo/again.mft"), + time::OffsetDateTime::UNIX_EPOCH, + SyncPreference::RrdpThenRsync, + 0, + ); + assert_eq!(action, RepoRequestAction::Reused(result_ref)); + } + + #[test] + fn complete_failure_releases_waiting_requesters_and_reuses_failure() { + let key = RepoKey::new("rsync://example.test/repo/", None); + let mut table = InFlightRepoTable::new(); + let _ = table.register_request( + key.clone(), + requester("arin", "arin", "rsync://example.test/repo/root.mft"), + time::OffsetDateTime::UNIX_EPOCH, + SyncPreference::RrdpThenRsync, + 0, + ); + table + .mark_running(&key, time::OffsetDateTime::UNIX_EPOCH) + .expect("mark running"); + let waiting = requester("ripe", "ripe", "rsync://example.test/repo/child.mft"); + let _ = table.register_request( + key.clone(), + waiting.clone(), + time::OffsetDateTime::UNIX_EPOCH, + SyncPreference::RrdpThenRsync, + 0, + ); + + let completion = table + .complete_failure( + &key, + RepoSyncResultEnvelope { + repo_key: key.clone(), + tal_id: "arin".to_string(), + rir_id: "arin".to_string(), + result: RepoSyncResultKind::Failed { + detail: "network timeout".to_string(), + }, + phase: Some("rrdp_failed_rsync_failed".to_string()), + timing_ms: 10, + warnings: Vec::new(), + }, + time::OffsetDateTime::UNIX_EPOCH, + ) + .expect("complete failure"); + assert_eq!(completion.released_requesters, vec![waiting]); + assert_eq!(table.get(&key).unwrap().state, RepoTaskState::Failed); + + let action = table.register_request( + key, + requester("afrinic", "afrinic", "rsync://example.test/repo/again.mft"), + time::OffsetDateTime::UNIX_EPOCH, + SyncPreference::RrdpThenRsync, + 0, + ); + assert_eq!( + action, + RepoRequestAction::FailedReuse { + detail: "network timeout".to_string() + } + ); + } + + #[test] + fn requesters_from_different_tals_do_not_lose_identity() { + let key = RepoKey::new("rsync://shared.example/repo/", None); + let mut table = InFlightRepoTable::new(); + let _ = table.register_request( + key.clone(), + requester("arin", "arin", "rsync://shared.example/repo/a.mft"), + time::OffsetDateTime::UNIX_EPOCH, + SyncPreference::RrdpThenRsync, + 0, + ); + table + .mark_running(&key, time::OffsetDateTime::UNIX_EPOCH) + .expect("mark running"); + let wait_apnic = requester("apnic", "apnic", "rsync://shared.example/repo/b.mft"); + let wait_ripe = requester("ripe", "ripe", "rsync://shared.example/repo/c.mft"); + let _ = table.register_request( + key.clone(), + wait_apnic.clone(), + time::OffsetDateTime::UNIX_EPOCH, + SyncPreference::RrdpThenRsync, + 0, + ); + let _ = table.register_request( + key.clone(), + wait_ripe.clone(), + time::OffsetDateTime::UNIX_EPOCH, + SyncPreference::RrdpThenRsync, + 0, + ); + let completion = table + .complete_success( + &key, + RepoSyncResultEnvelope { + repo_key: key.clone(), + tal_id: "arin".to_string(), + rir_id: "arin".to_string(), + result: RepoSyncResultKind::Success(RepoSyncResultRef { + repo_key: key.clone(), + source: "rrdp".to_string(), + }), + phase: Some("rrdp_ok".to_string()), + timing_ms: 10, + warnings: Vec::new(), + }, + time::OffsetDateTime::UNIX_EPOCH, + ) + .expect("complete success"); + assert_eq!(completion.released_requesters, vec![wait_apnic, wait_ripe]); + } +} diff --git a/src/scheduler/repo_scheduler/tests_transport.rs b/src/scheduler/repo_scheduler/tests_transport.rs new file mode 100644 index 0000000..941bb0b --- /dev/null +++ b/src/scheduler/repo_scheduler/tests_transport.rs @@ -0,0 +1,864 @@ +// Transport scheduler deduplication and fallback tests. + +#[cfg(test)] +mod transport_tests { + use crate::scheduler::repo_scheduler::{ + TransportRequestAction, TransportStateTables, TransportTaskState, + }; + use crate::scheduler::types::{ + RepoDedupKey, RepoIdentity, RepoRequester, RepoRuntimeState, RepoTransportMode, + RepoTransportResultEnvelope, RepoTransportResultKind, + }; + use crate::validation::policy::SyncPreference; + + fn requester(id: &str) -> RepoRequester { + RepoRequester::with_tal_rir( + "apnic", + "apnic", + format!("rsync://example.test/repo/{id}.mft"), + "rsync://example.test/repo/".to_string(), + format!("node:{id}"), + ) + } + + #[test] + fn register_transport_request_enqueues_initial_rrdp_task() { + let mut tables = TransportStateTables::new(); + let identity = RepoIdentity::new( + Some("https://example.test/notify.xml".to_string()), + "rsync://example.test/repo/", + ); + let action = tables.register_transport_request( + identity.clone(), + requester("root"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/module/".to_string(), + None, + SyncPreference::RrdpThenRsync, + ); + let task = match action { + TransportRequestAction::Enqueue(task) => task, + other => panic!("expected enqueue, got {other:?}"), + }; + assert_eq!(task.mode, RepoTransportMode::Rrdp); + assert_eq!( + task.dedup_key, + RepoDedupKey::RrdpNotify { + notification_uri: "https://example.test/notify.xml".to_string() + } + ); + let key = "https://example.test/notify.xml".to_string(); + assert_eq!( + tables.rrdp_inflight.get(&key).unwrap().state, + TransportTaskState::Pending + ); + assert_eq!( + tables.runtime_records.get(&identity).unwrap().state, + RepoRuntimeState::WaitingRrdp + ); + } + + #[test] + fn register_transport_request_waits_on_existing_rrdp_task() { + let mut tables = TransportStateTables::new(); + let identity = RepoIdentity::new( + Some("https://example.test/notify.xml".to_string()), + "rsync://example.test/repo/a/", + ); + let _ = tables.register_transport_request( + identity.clone(), + requester("a"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/module/".to_string(), + None, + SyncPreference::RrdpThenRsync, + ); + let other_identity = RepoIdentity::new( + Some("https://example.test/notify.xml".to_string()), + "rsync://example.test/repo/b/", + ); + let action = tables.register_transport_request( + other_identity.clone(), + requester("b"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/module/".to_string(), + None, + SyncPreference::RrdpThenRsync, + ); + assert_eq!( + action, + TransportRequestAction::Waiting { + state: RepoRuntimeState::WaitingRrdp + } + ); + assert_eq!( + tables.runtime_records.get(&other_identity).unwrap().state, + RepoRuntimeState::WaitingRrdp + ); + assert_eq!( + tables + .rrdp_inflight + .get("https://example.test/notify.xml") + .unwrap() + .waiting_requesters + .len(), + 1 + ); + } + + #[test] + fn complete_rrdp_success_reuses_for_later_identity_requests() { + let mut tables = TransportStateTables::new(); + let identity = RepoIdentity::new( + Some("https://example.test/notify.xml".to_string()), + "rsync://example.test/repo/a/", + ); + let _ = tables.register_transport_request( + identity.clone(), + requester("a"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/module/".to_string(), + None, + SyncPreference::RrdpThenRsync, + ); + tables + .mark_transport_running( + &RepoDedupKey::RrdpNotify { + notification_uri: "https://example.test/notify.xml".to_string(), + }, + time::OffsetDateTime::UNIX_EPOCH, + ) + .expect("mark running"); + let completion = tables + .complete_transport_result( + RepoTransportResultEnvelope { + dedup_key: RepoDedupKey::RrdpNotify { + notification_uri: "https://example.test/notify.xml".to_string(), + }, + rsync_failure_scope_uri: None, + repo_identity: identity.clone(), + mode: RepoTransportMode::Rrdp, + tal_id: "apnic".to_string(), + rir_id: "apnic".to_string(), + timing_ms: 10, + result: RepoTransportResultKind::Success { + source: "rrdp".to_string(), + warnings: Vec::new(), + }, + }, + time::OffsetDateTime::UNIX_EPOCH, + ) + .expect("complete success"); + assert!(completion.follow_up_tasks.is_empty()); + let later_identity = RepoIdentity::new( + Some("https://example.test/notify.xml".to_string()), + "rsync://example.test/repo/b/", + ); + let action = tables.register_transport_request( + later_identity.clone(), + requester("b"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/module/".to_string(), + None, + SyncPreference::RrdpThenRsync, + ); + assert!(matches!(action, TransportRequestAction::ReusedSuccess(_))); + assert_eq!( + tables.runtime_records.get(&later_identity).unwrap().state, + RepoRuntimeState::RrdpOk + ); + + let same_identity_action = tables.register_transport_request( + later_identity.clone(), + requester("b-again"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/module/".to_string(), + None, + SyncPreference::RrdpThenRsync, + ); + assert!(matches!( + same_identity_action, + TransportRequestAction::ReusedSuccess(_) + )); + + let finalized = tables.finalized_runtime_records_for_transport(&RepoDedupKey::RrdpNotify { + notification_uri: "https://example.test/notify.xml".to_string(), + }); + assert_eq!(finalized.len(), 2); + } + + #[test] + fn complete_rrdp_failure_enqueues_rsync_follow_up() { + let mut tables = TransportStateTables::new(); + let identity = RepoIdentity::new( + Some("https://example.test/notify.xml".to_string()), + "rsync://example.test/repo/a/", + ); + let _ = tables.register_transport_request( + identity.clone(), + requester("a"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/module/".to_string(), + None, + SyncPreference::RrdpThenRsync, + ); + tables + .mark_transport_running( + &RepoDedupKey::RrdpNotify { + notification_uri: "https://example.test/notify.xml".to_string(), + }, + time::OffsetDateTime::UNIX_EPOCH, + ) + .expect("mark running"); + let completion = tables + .complete_transport_result( + RepoTransportResultEnvelope { + dedup_key: RepoDedupKey::RrdpNotify { + notification_uri: "https://example.test/notify.xml".to_string(), + }, + rsync_failure_scope_uri: None, + repo_identity: identity.clone(), + mode: RepoTransportMode::Rrdp, + tal_id: "apnic".to_string(), + rir_id: "apnic".to_string(), + timing_ms: 10, + result: RepoTransportResultKind::Failed { + detail: "rrdp timeout".to_string(), + warnings: Vec::new(), + error_class: crate::scheduler::types::RepoTransportErrorClass::Unknown, + }, + }, + time::OffsetDateTime::UNIX_EPOCH, + ) + .expect("complete failure"); + assert_eq!(completion.follow_up_tasks.len(), 1); + assert_eq!(completion.follow_up_tasks[0].mode, RepoTransportMode::Rsync); + assert_eq!( + tables.runtime_records.get(&identity).unwrap().state, + RepoRuntimeState::WaitingRsync + ); + + let same_identity_action = tables.register_transport_request( + identity.clone(), + requester("a-again"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/module/".to_string(), + None, + SyncPreference::RrdpThenRsync, + ); + assert_eq!( + same_identity_action, + TransportRequestAction::Waiting { + state: RepoRuntimeState::WaitingRsync + } + ); + + let later_identity = RepoIdentity::new( + Some("https://example.test/notify.xml".to_string()), + "rsync://example.test/repo/b/", + ); + let later_action = tables.register_transport_request( + later_identity.clone(), + requester("b"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/module/".to_string(), + None, + SyncPreference::RrdpThenRsync, + ); + assert_eq!( + later_action, + TransportRequestAction::Waiting { + state: RepoRuntimeState::WaitingRsync + } + ); + assert_eq!( + tables.runtime_records.get(&later_identity).unwrap().state, + RepoRuntimeState::WaitingRsync + ); + } + + #[test] + fn host_failure_scope_probes_once_then_reuses_terminal_failure() { + let mut tables = TransportStateTables::new(); + let identity_a = RepoIdentity::new( + Some("https://example.test/notify.xml".to_string()), + "rsync://example.test/repo/a/", + ); + let identity_b = RepoIdentity::new( + Some("https://example.test/notify.xml".to_string()), + "rsync://example.test/repo/b/", + ); + let _ = tables.register_transport_request( + identity_a.clone(), + requester("a"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/repo/a/".to_string(), + Some("rsync://example.test/".to_string()), + SyncPreference::RrdpThenRsync, + ); + let _ = tables.register_transport_request( + identity_b.clone(), + requester("b"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/repo/b/".to_string(), + Some("rsync://example.test/".to_string()), + SyncPreference::RrdpThenRsync, + ); + tables + .mark_transport_running( + &RepoDedupKey::RrdpNotify { + notification_uri: "https://example.test/notify.xml".to_string(), + }, + time::OffsetDateTime::UNIX_EPOCH, + ) + .expect("mark running"); + let completion = tables + .complete_transport_result( + RepoTransportResultEnvelope { + dedup_key: RepoDedupKey::RrdpNotify { + notification_uri: "https://example.test/notify.xml".to_string(), + }, + rsync_failure_scope_uri: None, + repo_identity: identity_a.clone(), + mode: RepoTransportMode::Rrdp, + tal_id: "apnic".to_string(), + rir_id: "apnic".to_string(), + timing_ms: 10, + result: RepoTransportResultKind::Failed { + detail: "rrdp timeout".to_string(), + warnings: Vec::new(), + error_class: crate::scheduler::types::RepoTransportErrorClass::Unknown, + }, + }, + time::OffsetDateTime::UNIX_EPOCH, + ) + .expect("complete rrdp failure"); + assert_eq!(completion.follow_up_tasks.len(), 1); + assert_eq!( + completion.follow_up_tasks[0] + .rsync_failure_scope_uri + .as_deref(), + Some("rsync://example.test/") + ); + let probe_task = completion.follow_up_tasks[0].clone(); + + tables + .mark_transport_running(&probe_task.dedup_key, time::OffsetDateTime::UNIX_EPOCH) + .expect("mark rsync running"); + let completion = tables + .complete_transport_result( + RepoTransportResultEnvelope { + dedup_key: probe_task.dedup_key, + rsync_failure_scope_uri: probe_task.rsync_failure_scope_uri, + repo_identity: probe_task.repo_identity, + mode: RepoTransportMode::Rsync, + tal_id: "apnic".to_string(), + rir_id: "apnic".to_string(), + timing_ms: 15_000, + result: RepoTransportResultKind::Failed { + detail: "rsync error: timeout waiting for daemon connection".to_string(), + warnings: Vec::new(), + error_class: crate::scheduler::types::RepoTransportErrorClass::Unknown, + }, + }, + time::OffsetDateTime::UNIX_EPOCH, + ) + .expect("complete rsync failure"); + assert!(completion.follow_up_tasks.is_empty()); + assert_eq!( + tables.runtime_records.get(&identity_a).unwrap().state, + RepoRuntimeState::FailedTerminal + ); + assert_eq!( + tables.runtime_records.get(&identity_b).unwrap().state, + RepoRuntimeState::FailedTerminal + ); + let finalized = tables.finalized_runtime_records_for_transport_result( + tables + .runtime_records + .get(&identity_a) + .unwrap() + .terminal_failure + .as_ref() + .unwrap(), + ); + assert_eq!(finalized.len(), 2); + } + + #[test] + fn complete_rsync_failure_reuses_terminal_failure() { + let mut tables = TransportStateTables::new(); + let identity = RepoIdentity::new(None, "rsync://example.test/repo/a/"); + let _ = tables.register_transport_request( + identity.clone(), + requester("a"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/module/".to_string(), + None, + SyncPreference::RsyncOnly, + ); + tables + .mark_transport_running( + &RepoDedupKey::RsyncScope { + rsync_scope_uri: "rsync://example.test/module/".to_string(), + }, + time::OffsetDateTime::UNIX_EPOCH, + ) + .expect("mark running"); + let _ = tables + .complete_transport_result( + RepoTransportResultEnvelope { + dedup_key: RepoDedupKey::RsyncScope { + rsync_scope_uri: "rsync://example.test/module/".to_string(), + }, + rsync_failure_scope_uri: None, + repo_identity: identity.clone(), + mode: RepoTransportMode::Rsync, + tal_id: "apnic".to_string(), + rir_id: "apnic".to_string(), + timing_ms: 20, + result: RepoTransportResultKind::Failed { + detail: "rsync timeout".to_string(), + warnings: Vec::new(), + error_class: crate::scheduler::types::RepoTransportErrorClass::Unknown, + }, + }, + time::OffsetDateTime::UNIX_EPOCH, + ) + .expect("complete rsync failure"); + let later_identity = RepoIdentity::new(None, "rsync://example.test/repo/b/"); + let action = tables.register_transport_request( + later_identity, + requester("b"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/module/".to_string(), + None, + SyncPreference::RsyncOnly, + ); + assert!(matches!( + action, + TransportRequestAction::ReusedTerminalFailure(_) + )); + + let finalized = tables.finalized_runtime_records_for_transport(&RepoDedupKey::RsyncScope { + rsync_scope_uri: "rsync://example.test/module/".to_string(), + }); + assert_eq!(finalized.len(), 2); + + let same_identity_action = tables.register_transport_request( + identity, + requester("a-again"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/module/".to_string(), + None, + SyncPreference::RsyncOnly, + ); + assert!(matches!( + same_identity_action, + TransportRequestAction::ReusedTerminalFailure(_) + )); + } + + #[test] + fn complete_rsync_success_reuses_for_later_identity_requests() { + let mut tables = TransportStateTables::new(); + let identity = RepoIdentity::new(None, "rsync://example.test/repo/a/"); + let action = tables.register_transport_request( + identity.clone(), + requester("a"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/module/".to_string(), + None, + SyncPreference::RsyncOnly, + ); + assert!(matches!(action, TransportRequestAction::Enqueue(_))); + tables + .mark_transport_running( + &RepoDedupKey::RsyncScope { + rsync_scope_uri: "rsync://example.test/module/".to_string(), + }, + time::OffsetDateTime::UNIX_EPOCH, + ) + .expect("mark running"); + tables + .complete_transport_result( + RepoTransportResultEnvelope { + dedup_key: RepoDedupKey::RsyncScope { + rsync_scope_uri: "rsync://example.test/module/".to_string(), + }, + rsync_failure_scope_uri: None, + repo_identity: identity.clone(), + mode: RepoTransportMode::Rsync, + tal_id: "apnic".to_string(), + rir_id: "apnic".to_string(), + timing_ms: 20, + result: RepoTransportResultKind::Success { + source: "rsync".to_string(), + warnings: Vec::new(), + }, + }, + time::OffsetDateTime::UNIX_EPOCH, + ) + .expect("complete rsync success"); + + let later_identity = RepoIdentity::new(None, "rsync://example.test/repo/b/"); + let action = tables.register_transport_request( + later_identity.clone(), + requester("b"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/module/".to_string(), + None, + SyncPreference::RsyncOnly, + ); + assert!(matches!(action, TransportRequestAction::ReusedSuccess(_))); + assert_eq!( + tables.runtime_records.get(&later_identity).unwrap().state, + RepoRuntimeState::RsyncOk + ); + } + + #[test] + fn register_rsync_request_waits_on_existing_rsync_task() { + let mut tables = TransportStateTables::new(); + let identity_a = RepoIdentity::new(None, "rsync://example.test/repo/a/"); + let identity_b = RepoIdentity::new(None, "rsync://example.test/repo/b/"); + let _ = tables.register_transport_request( + identity_a, + requester("a"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/module/".to_string(), + None, + SyncPreference::RsyncOnly, + ); + let action = tables.register_transport_request( + identity_b.clone(), + requester("b"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/module/".to_string(), + None, + SyncPreference::RsyncOnly, + ); + assert_eq!( + action, + TransportRequestAction::Waiting { + state: RepoRuntimeState::WaitingRsync + } + ); + assert_eq!( + tables.runtime_records.get(&identity_b).unwrap().state, + RepoRuntimeState::WaitingRsync + ); + assert_eq!( + tables + .rsync_inflight + .get("rsync://example.test/module/") + .unwrap() + .waiting_requesters + .len(), + 1 + ); + } + + #[test] + fn host_failure_scope_success_marks_host_reachable_and_schedules_waiters() { + let mut tables = TransportStateTables::new(); + let identity_a = RepoIdentity::new(None, "rsync://example.test/repo/a/"); + let identity_b = RepoIdentity::new(None, "rsync://example.test/repo/b/"); + let action_a = tables.register_transport_request( + identity_a.clone(), + requester("a"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/repo/a/".to_string(), + Some("rsync://example.test/".to_string()), + SyncPreference::RsyncOnly, + ); + let probe_task = match action_a { + TransportRequestAction::Enqueue(task) => task, + other => panic!("expected first host probe enqueue, got {other:?}"), + }; + let action_b = tables.register_transport_request( + identity_b.clone(), + requester("b"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/repo/b/".to_string(), + Some("rsync://example.test/".to_string()), + SyncPreference::RsyncOnly, + ); + assert_eq!( + action_b, + TransportRequestAction::Waiting { + state: RepoRuntimeState::WaitingRsync + } + ); + + tables + .mark_transport_running(&probe_task.dedup_key, time::OffsetDateTime::UNIX_EPOCH) + .expect("mark rsync running"); + let completion = tables + .complete_transport_result( + RepoTransportResultEnvelope { + dedup_key: probe_task.dedup_key, + rsync_failure_scope_uri: probe_task.rsync_failure_scope_uri, + repo_identity: probe_task.repo_identity, + mode: RepoTransportMode::Rsync, + tal_id: "apnic".to_string(), + rir_id: "apnic".to_string(), + timing_ms: 5, + result: RepoTransportResultKind::Success { + source: "rsync".to_string(), + warnings: Vec::new(), + }, + }, + time::OffsetDateTime::UNIX_EPOCH, + ) + .expect("complete rsync success"); + + assert_eq!( + tables.runtime_records.get(&identity_a).unwrap().state, + RepoRuntimeState::RsyncOk + ); + assert_eq!(completion.follow_up_tasks.len(), 1); + assert_eq!( + completion.follow_up_tasks[0].dedup_key, + RepoDedupKey::RsyncScope { + rsync_scope_uri: "rsync://example.test/repo/b/".to_string() + } + ); + assert_eq!( + tables.runtime_records.get(&identity_b).unwrap().state, + RepoRuntimeState::WaitingRsync + ); + } + + #[test] + fn non_host_level_rsync_failure_does_not_poison_host_scope() { + let mut tables = TransportStateTables::new(); + let identity_a = RepoIdentity::new(None, "rsync://example.test/repo/a/"); + let identity_b = RepoIdentity::new(None, "rsync://example.test/repo/b/"); + let action_a = tables.register_transport_request( + identity_a.clone(), + requester("a"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/repo/a/".to_string(), + Some("rsync://example.test/".to_string()), + SyncPreference::RsyncOnly, + ); + let probe_task = match action_a { + TransportRequestAction::Enqueue(task) => task, + other => panic!("expected first host probe enqueue, got {other:?}"), + }; + let _ = tables.register_transport_request( + identity_b.clone(), + requester("b"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/repo/b/".to_string(), + Some("rsync://example.test/".to_string()), + SyncPreference::RsyncOnly, + ); + + tables + .mark_transport_running(&probe_task.dedup_key, time::OffsetDateTime::UNIX_EPOCH) + .expect("mark rsync running"); + let completion = tables + .complete_transport_result( + RepoTransportResultEnvelope { + dedup_key: probe_task.dedup_key, + rsync_failure_scope_uri: probe_task.rsync_failure_scope_uri, + repo_identity: probe_task.repo_identity, + mode: RepoTransportMode::Rsync, + tal_id: "apnic".to_string(), + rir_id: "apnic".to_string(), + timing_ms: 5, + result: RepoTransportResultKind::Failed { + detail: "rsync file digest mismatch after download".to_string(), + warnings: Vec::new(), + error_class: crate::scheduler::types::RepoTransportErrorClass::Unknown, + }, + }, + time::OffsetDateTime::UNIX_EPOCH, + ) + .expect("complete rsync failure"); + + assert_eq!( + tables.runtime_records.get(&identity_a).unwrap().state, + RepoRuntimeState::FailedTerminal + ); + assert_eq!(completion.follow_up_tasks.len(), 1); + assert_eq!( + completion.follow_up_tasks[0].dedup_key, + RepoDedupKey::RsyncScope { + rsync_scope_uri: "rsync://example.test/repo/b/".to_string() + } + ); + } + + #[test] + fn cached_host_level_failure_reuses_for_later_rsync_only_requests() { + let mut tables = TransportStateTables::new(); + let identity_a = RepoIdentity::new(None, "rsync://example.test/repo/a/"); + let action_a = tables.register_transport_request( + identity_a, + requester("a"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/repo/a/".to_string(), + Some("rsync://example.test/".to_string()), + SyncPreference::RsyncOnly, + ); + let probe_task = match action_a { + TransportRequestAction::Enqueue(task) => task, + other => panic!("expected first host probe enqueue, got {other:?}"), + }; + tables + .mark_transport_running(&probe_task.dedup_key, time::OffsetDateTime::UNIX_EPOCH) + .expect("mark rsync running"); + let _ = tables + .complete_transport_result( + RepoTransportResultEnvelope { + dedup_key: probe_task.dedup_key, + rsync_failure_scope_uri: probe_task.rsync_failure_scope_uri, + repo_identity: probe_task.repo_identity, + mode: RepoTransportMode::Rsync, + tal_id: "apnic".to_string(), + rir_id: "apnic".to_string(), + timing_ms: 15_000, + result: RepoTransportResultKind::Failed { + detail: "rsync error: failed to connect to daemon".to_string(), + warnings: Vec::new(), + error_class: crate::scheduler::types::RepoTransportErrorClass::Unknown, + }, + }, + time::OffsetDateTime::UNIX_EPOCH, + ) + .expect("complete host failure"); + + let later_identity = RepoIdentity::new(None, "rsync://example.test/repo/c/"); + let action = tables.register_transport_request( + later_identity.clone(), + requester("c"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/repo/c/".to_string(), + Some("rsync://example.test/".to_string()), + SyncPreference::RsyncOnly, + ); + assert!(matches!( + action, + TransportRequestAction::ReusedTerminalFailure(_) + )); + assert_eq!( + tables.runtime_records.get(&later_identity).unwrap().state, + RepoRuntimeState::FailedTerminal + ); + } + + #[test] + fn reset_run_state_clears_host_failure_scope_cache() { + let mut tables = TransportStateTables::new(); + let identity = RepoIdentity::new(None, "rsync://example.test/repo/a/"); + let action = tables.register_transport_request( + identity, + requester("a"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/repo/a/".to_string(), + Some("rsync://example.test/".to_string()), + SyncPreference::RsyncOnly, + ); + let probe_task = match action { + TransportRequestAction::Enqueue(task) => task, + other => panic!("expected host probe enqueue, got {other:?}"), + }; + tables + .mark_transport_running(&probe_task.dedup_key, time::OffsetDateTime::UNIX_EPOCH) + .expect("mark rsync running"); + let _ = tables + .complete_transport_result( + RepoTransportResultEnvelope { + dedup_key: probe_task.dedup_key, + rsync_failure_scope_uri: probe_task.rsync_failure_scope_uri, + repo_identity: probe_task.repo_identity, + mode: RepoTransportMode::Rsync, + tal_id: "apnic".to_string(), + rir_id: "apnic".to_string(), + timing_ms: 15_000, + result: RepoTransportResultKind::Failed { + detail: "temporary failure in name resolution".to_string(), + warnings: Vec::new(), + error_class: crate::scheduler::types::RepoTransportErrorClass::Unknown, + }, + }, + time::OffsetDateTime::UNIX_EPOCH, + ) + .expect("complete host failure"); + + tables.reset_run_state(); + let later_identity = RepoIdentity::new(None, "rsync://example.test/repo/b/"); + let action = tables.register_transport_request( + later_identity, + requester("b"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/repo/b/".to_string(), + Some("rsync://example.test/".to_string()), + SyncPreference::RsyncOnly, + ); + assert!(matches!(action, TransportRequestAction::Enqueue(_))); + } + + #[test] + fn register_transport_request_skips_rrdp_when_sync_preference_is_rsync_only() { + let mut tables = TransportStateTables::new(); + let identity = RepoIdentity::new( + Some("https://example.test/notify.xml".to_string()), + "rsync://example.test/repo/", + ); + let action = tables.register_transport_request( + identity.clone(), + requester("root"), + time::OffsetDateTime::UNIX_EPOCH, + 0, + "rsync://example.test/module/".to_string(), + None, + SyncPreference::RsyncOnly, + ); + let task = match action { + TransportRequestAction::Enqueue(task) => task, + other => panic!("expected enqueue, got {other:?}"), + }; + assert_eq!(task.mode, RepoTransportMode::Rsync); + assert_eq!( + task.dedup_key, + RepoDedupKey::RsyncScope { + rsync_scope_uri: "rsync://example.test/module/".to_string() + } + ); + assert!(tables.rrdp_inflight.is_empty()); + assert_eq!( + tables.runtime_records.get(&identity).unwrap().state, + RepoRuntimeState::WaitingRsync + ); + } +} diff --git a/src/scheduler/repo_scheduler/transport_state.rs b/src/scheduler/repo_scheduler/transport_state.rs new file mode 100644 index 0000000..bd106e0 --- /dev/null +++ b/src/scheduler/repo_scheduler/transport_state.rs @@ -0,0 +1,719 @@ +// Transport request state tables and completion transitions. + +impl TransportStateTables { + pub fn new() -> Self { + Self::default() + } + + pub fn runtime_record(&self, identity: &RepoIdentity) -> Option<&RepoRuntimeRecord> { + self.runtime_records.get(identity) + } + + pub fn finalized_runtime_records_for_transport( + &self, + dedup_key: &RepoDedupKey, + ) -> Vec { + self.runtime_records + .values() + .filter(|record| match dedup_key { + RepoDedupKey::RrdpNotify { notification_uri } => { + record.rrdp_notification_key.as_deref() == Some(notification_uri.as_str()) + } + RepoDedupKey::RsyncScope { rsync_scope_uri } => { + record.rsync_scope_key == *rsync_scope_uri + } + }) + .filter(|record| { + matches!( + record.state, + RepoRuntimeState::RrdpOk + | RepoRuntimeState::RsyncOk + | RepoRuntimeState::FailedTerminal + ) + }) + .cloned() + .collect() + } + + pub fn finalized_runtime_records_for_transport_result( + &self, + result: &RepoTransportResultEnvelope, + ) -> Vec { + let failure_scope = reusable_rsync_failure_scope(result); + self.runtime_records + .values() + .filter(|record| { + let exact_match = match &result.dedup_key { + RepoDedupKey::RrdpNotify { notification_uri } => { + record.rrdp_notification_key.as_deref() == Some(notification_uri.as_str()) + } + RepoDedupKey::RsyncScope { rsync_scope_uri } => { + record.rsync_scope_key == *rsync_scope_uri + } + }; + let failure_scope_match = failure_scope + .map(|scope| record.rsync_failure_scope_key.as_deref() == Some(scope)) + .unwrap_or(false); + exact_match || failure_scope_match + }) + .filter(|record| { + matches!( + record.state, + RepoRuntimeState::RrdpOk + | RepoRuntimeState::RsyncOk + | RepoRuntimeState::FailedTerminal + ) + }) + .cloned() + .collect() + } + + pub fn reset_run_state(&mut self) { + self.rrdp_inflight.clear(); + self.rsync_inflight.clear(); + self.rsync_failure_by_scope.clear(); + self.rsync_failure_probe_inflight.clear(); + self.rsync_failure_scope_reachable.clear(); + self.runtime_records.clear(); + } + + pub fn register_transport_request( + &mut self, + identity: RepoIdentity, + requester: RepoRequester, + validation_time: time::OffsetDateTime, + priority: u8, + rsync_scope_uri: String, + rsync_failure_scope_uri: Option, + sync_preference: SyncPreference, + ) -> TransportRequestAction { + if let Some(record) = self.runtime_records.get_mut(&identity) { + record.requesters.push(requester.clone()); + return match record.state { + RepoRuntimeState::WaitingRrdp => { + if let Some(key) = record.rrdp_notification_key.as_ref() + && let Some(entry) = self.rrdp_inflight.get_mut(key) { + entry.waiting_requesters.push(requester); + } + TransportRequestAction::Waiting { + state: RepoRuntimeState::WaitingRrdp, + } + } + RepoRuntimeState::RrdpOk | RepoRuntimeState::RsyncOk => { + TransportRequestAction::ReusedSuccess( + record + .last_success + .clone() + .expect("success state must keep last_success"), + ) + } + RepoRuntimeState::RrdpFailedPendingRsync | RepoRuntimeState::WaitingRsync => { + if let Some(entry) = self.rsync_inflight.get_mut(&record.rsync_scope_key) { + entry.waiting_requesters.push(requester); + } + record.state = RepoRuntimeState::WaitingRsync; + TransportRequestAction::Waiting { + state: RepoRuntimeState::WaitingRsync, + } + } + RepoRuntimeState::FailedTerminal => TransportRequestAction::ReusedTerminalFailure( + record + .terminal_failure + .clone() + .expect("terminal failure must keep last result"), + ), + RepoRuntimeState::Init => TransportRequestAction::Waiting { + state: RepoRuntimeState::Init, + }, + }; + } + + if sync_preference == SyncPreference::RrdpThenRsync + && let Some(notification_uri) = identity.notification_uri.clone() { + if let Some(entry) = self.rrdp_inflight.get_mut(¬ification_uri) { + if let Some(result) = entry.last_result.clone() { + return match result.result { + RepoTransportResultKind::Success { .. } => { + self.runtime_records.insert( + identity.clone(), + RepoRuntimeRecord { + identity, + state: RepoRuntimeState::RrdpOk, + rrdp_notification_key: Some(notification_uri), + rsync_scope_key: rsync_scope_uri, + rsync_failure_scope_key: rsync_failure_scope_uri.clone(), + requesters: vec![requester], + validation_time, + priority, + last_success: Some(result.clone()), + terminal_failure: None, + }, + ); + TransportRequestAction::ReusedSuccess(result) + } + RepoTransportResultKind::Failed { .. } => { + self.runtime_records.insert( + identity.clone(), + RepoRuntimeRecord { + identity: identity.clone(), + state: RepoRuntimeState::RrdpFailedPendingRsync, + rrdp_notification_key: Some(notification_uri), + rsync_scope_key: rsync_scope_uri.clone(), + rsync_failure_scope_key: rsync_failure_scope_uri.clone(), + requesters: vec![requester.clone()], + validation_time, + priority, + last_success: None, + terminal_failure: None, + }, + ); + self.register_rsync_request( + identity, + requester, + validation_time, + priority, + rsync_scope_uri, + rsync_failure_scope_uri, + ) + } + }; + } + + entry.waiting_requesters.push(requester.clone()); + self.runtime_records.insert( + identity.clone(), + RepoRuntimeRecord { + identity, + state: RepoRuntimeState::WaitingRrdp, + rrdp_notification_key: Some(notification_uri), + rsync_scope_key: rsync_scope_uri, + rsync_failure_scope_key: rsync_failure_scope_uri.clone(), + requesters: vec![requester], + validation_time, + priority, + last_success: None, + terminal_failure: None, + }, + ); + return TransportRequestAction::Waiting { + state: RepoRuntimeState::WaitingRrdp, + }; + } + + let task = RepoTransportTask { + dedup_key: RepoDedupKey::RrdpNotify { + notification_uri: notification_uri.clone(), + }, + rsync_failure_scope_uri: None, + repo_identity: identity.clone(), + mode: RepoTransportMode::Rrdp, + retry_short_timeout: false, + tal_id: requester.tal_id.clone(), + rir_id: requester.rir_id.clone(), + validation_time, + priority, + requesters: vec![requester.clone()], + }; + self.rrdp_inflight.insert( + notification_uri.clone(), + TransportInFlightEntry { + state: TransportTaskState::Pending, + task: task.clone(), + waiting_requesters: Vec::new(), + last_result: None, + started_at: None, + finished_at: None, + }, + ); + self.runtime_records.insert( + identity.clone(), + RepoRuntimeRecord { + identity, + state: RepoRuntimeState::WaitingRrdp, + rrdp_notification_key: Some(notification_uri), + rsync_scope_key: rsync_scope_uri, + rsync_failure_scope_key: rsync_failure_scope_uri.clone(), + requesters: vec![requester], + validation_time, + priority, + last_success: None, + terminal_failure: None, + }, + ); + return TransportRequestAction::Enqueue(task); + } + + self.register_rsync_request( + identity, + requester, + validation_time, + priority, + rsync_scope_uri, + rsync_failure_scope_uri, + ) + } + + fn register_rsync_request( + &mut self, + identity: RepoIdentity, + requester: RepoRequester, + validation_time: time::OffsetDateTime, + priority: u8, + rsync_scope_uri: String, + rsync_failure_scope_uri: Option, + ) -> TransportRequestAction { + if let Some(entry) = self.rsync_inflight.get_mut(&rsync_scope_uri) { + if let Some(result) = entry.last_result.clone() { + return match result.result { + RepoTransportResultKind::Success { .. } => { + self.runtime_records.insert( + identity.clone(), + RepoRuntimeRecord { + identity, + state: RepoRuntimeState::RsyncOk, + rrdp_notification_key: None, + rsync_scope_key: rsync_scope_uri, + rsync_failure_scope_key: rsync_failure_scope_uri.clone(), + requesters: vec![requester], + validation_time, + priority, + last_success: Some(result.clone()), + terminal_failure: None, + }, + ); + TransportRequestAction::ReusedSuccess(result) + } + RepoTransportResultKind::Failed { .. } => { + self.runtime_records.insert( + identity.clone(), + RepoRuntimeRecord { + identity, + state: RepoRuntimeState::FailedTerminal, + rrdp_notification_key: None, + rsync_scope_key: rsync_scope_uri, + rsync_failure_scope_key: rsync_failure_scope_uri.clone(), + requesters: vec![requester], + validation_time, + priority, + last_success: None, + terminal_failure: Some(result.clone()), + }, + ); + TransportRequestAction::ReusedTerminalFailure(result) + } + }; + } + + entry.waiting_requesters.push(requester.clone()); + self.runtime_records.insert( + identity.clone(), + RepoRuntimeRecord { + identity, + state: RepoRuntimeState::WaitingRsync, + rrdp_notification_key: None, + rsync_scope_key: rsync_scope_uri, + rsync_failure_scope_key: rsync_failure_scope_uri.clone(), + requesters: vec![requester], + validation_time, + priority, + last_success: None, + terminal_failure: None, + }, + ); + return TransportRequestAction::Waiting { + state: RepoRuntimeState::WaitingRsync, + }; + } + + if let Some(failure_scope_uri) = rsync_failure_scope_uri.as_ref() { + if let Some(result) = self.rsync_failure_by_scope.get(failure_scope_uri).cloned() { + self.runtime_records.insert( + identity.clone(), + RepoRuntimeRecord { + identity, + state: RepoRuntimeState::FailedTerminal, + rrdp_notification_key: None, + rsync_scope_key: rsync_scope_uri, + rsync_failure_scope_key: rsync_failure_scope_uri.clone(), + requesters: vec![requester], + validation_time, + priority, + last_success: None, + terminal_failure: Some(result.clone()), + }, + ); + return TransportRequestAction::ReusedTerminalFailure(result); + } + + if !self + .rsync_failure_scope_reachable + .contains(failure_scope_uri) + && self + .rsync_failure_probe_inflight + .contains_key(failure_scope_uri) + { + self.runtime_records.insert( + identity.clone(), + RepoRuntimeRecord { + identity, + state: RepoRuntimeState::WaitingRsync, + rrdp_notification_key: None, + rsync_scope_key: rsync_scope_uri, + rsync_failure_scope_key: rsync_failure_scope_uri.clone(), + requesters: vec![requester], + validation_time, + priority, + last_success: None, + terminal_failure: None, + }, + ); + return TransportRequestAction::Waiting { + state: RepoRuntimeState::WaitingRsync, + }; + } + } + + let task = RepoTransportTask { + dedup_key: RepoDedupKey::RsyncScope { + rsync_scope_uri: rsync_scope_uri.clone(), + }, + rsync_failure_scope_uri: rsync_failure_scope_uri.clone(), + repo_identity: identity.clone(), + mode: RepoTransportMode::Rsync, + retry_short_timeout: false, + tal_id: requester.tal_id.clone(), + rir_id: requester.rir_id.clone(), + validation_time, + priority, + requesters: vec![requester.clone()], + }; + self.rsync_inflight.insert( + rsync_scope_uri.clone(), + TransportInFlightEntry { + state: TransportTaskState::Pending, + task: task.clone(), + waiting_requesters: Vec::new(), + last_result: None, + started_at: None, + finished_at: None, + }, + ); + if let Some(failure_scope_uri) = rsync_failure_scope_uri.as_ref() + && !self + .rsync_failure_scope_reachable + .contains(failure_scope_uri) + { + self.rsync_failure_probe_inflight + .insert(failure_scope_uri.clone(), rsync_scope_uri.clone()); + } + self.runtime_records.insert( + identity.clone(), + RepoRuntimeRecord { + identity, + state: RepoRuntimeState::WaitingRsync, + rrdp_notification_key: None, + rsync_scope_key: rsync_scope_uri, + rsync_failure_scope_key: rsync_failure_scope_uri.clone(), + requesters: vec![requester], + validation_time, + priority, + last_success: None, + terminal_failure: None, + }, + ); + TransportRequestAction::Enqueue(task) + } + + fn schedule_rsync_for_record( + record: &mut RepoRuntimeRecord, + rsync_inflight: &mut HashMap, + rsync_failure_by_scope: &HashMap, + rsync_failure_probe_inflight: &mut HashMap, + rsync_failure_scope_reachable: &HashSet, + follow_up_tasks: &mut Vec, + ) { + let rsync_scope_uri = record.rsync_scope_key.clone(); + if let Some(entry) = rsync_inflight.get_mut(&rsync_scope_uri) { + if let Some(result) = entry.last_result.clone() { + match result.result { + RepoTransportResultKind::Success { .. } => { + record.state = RepoRuntimeState::RsyncOk; + record.last_success = Some(result); + } + RepoTransportResultKind::Failed { .. } => { + record.state = RepoRuntimeState::FailedTerminal; + record.terminal_failure = Some(result); + } + } + return; + } + entry.waiting_requesters.extend(record.requesters.clone()); + record.state = RepoRuntimeState::WaitingRsync; + return; + } + + if let Some(failure_scope_uri) = record.rsync_failure_scope_key.as_ref() { + if let Some(result) = rsync_failure_by_scope.get(failure_scope_uri).cloned() { + record.state = RepoRuntimeState::FailedTerminal; + record.terminal_failure = Some(result); + return; + } + if !rsync_failure_scope_reachable.contains(failure_scope_uri) + && rsync_failure_probe_inflight.contains_key(failure_scope_uri) + { + record.state = RepoRuntimeState::WaitingRsync; + return; + } + } + + let first_requester = record + .requesters + .first() + .expect("rsync record must keep at least one requester"); + let task = RepoTransportTask { + dedup_key: RepoDedupKey::RsyncScope { + rsync_scope_uri: rsync_scope_uri.clone(), + }, + rsync_failure_scope_uri: record.rsync_failure_scope_key.clone(), + repo_identity: record.identity.clone(), + mode: RepoTransportMode::Rsync, + retry_short_timeout: false, + tal_id: first_requester.tal_id.clone(), + rir_id: first_requester.rir_id.clone(), + validation_time: record.validation_time, + priority: record.priority, + requesters: record.requesters.clone(), + }; + rsync_inflight.insert( + rsync_scope_uri.clone(), + TransportInFlightEntry { + state: TransportTaskState::Pending, + task: task.clone(), + waiting_requesters: Vec::new(), + last_result: None, + started_at: None, + finished_at: None, + }, + ); + if let Some(failure_scope_uri) = record.rsync_failure_scope_key.as_ref() + && !rsync_failure_scope_reachable.contains(failure_scope_uri) { + rsync_failure_probe_inflight.insert(failure_scope_uri.clone(), rsync_scope_uri); + } + record.state = RepoRuntimeState::WaitingRsync; + follow_up_tasks.push(task); + } + + pub fn mark_transport_running( + &mut self, + dedup_key: &RepoDedupKey, + started_at: time::OffsetDateTime, + ) -> Result<(), String> { + match dedup_key { + RepoDedupKey::RrdpNotify { notification_uri } => { + let entry = self + .rrdp_inflight + .get_mut(notification_uri) + .ok_or_else(|| format!("rrdp transport not found: {notification_uri}"))?; + entry.state = TransportTaskState::Running; + entry.started_at = Some(started_at); + } + RepoDedupKey::RsyncScope { rsync_scope_uri } => { + let entry = self + .rsync_inflight + .get_mut(rsync_scope_uri) + .ok_or_else(|| format!("rsync transport not found: {rsync_scope_uri}"))?; + entry.state = TransportTaskState::Running; + entry.started_at = Some(started_at); + } + } + Ok(()) + } + + pub fn complete_transport_result( + &mut self, + result: RepoTransportResultEnvelope, + finished_at: time::OffsetDateTime, + ) -> Result { + match (&result.dedup_key, &result.result) { + ( + RepoDedupKey::RrdpNotify { notification_uri }, + RepoTransportResultKind::Success { .. }, + ) => { + let entry = self + .rrdp_inflight + .get_mut(notification_uri) + .ok_or_else(|| format!("rrdp transport not found: {notification_uri}"))?; + entry.state = TransportTaskState::Finished; + entry.finished_at = Some(finished_at); + entry.last_result = Some(result.clone()); + let released_requesters = std::mem::take(&mut entry.waiting_requesters); + for record in self.runtime_records.values_mut() { + if record.rrdp_notification_key.as_deref() == Some(notification_uri) + && record.state == RepoRuntimeState::WaitingRrdp + { + record.state = RepoRuntimeState::RrdpOk; + record.last_success = Some(result.clone()); + } + } + Ok(TransportCompletion { + released_requesters, + follow_up_tasks: Vec::new(), + }) + } + ( + RepoDedupKey::RrdpNotify { notification_uri }, + RepoTransportResultKind::Failed { .. }, + ) => { + let entry = self + .rrdp_inflight + .get_mut(notification_uri) + .ok_or_else(|| format!("rrdp transport not found: {notification_uri}"))?; + entry.state = TransportTaskState::Finished; + entry.finished_at = Some(finished_at); + entry.last_result = Some(result.clone()); + let mut follow_up_tasks = Vec::new(); + for record in self.runtime_records.values_mut() { + if record.rrdp_notification_key.as_deref() == Some(notification_uri) + && record.state == RepoRuntimeState::WaitingRrdp + { + record.state = RepoRuntimeState::RrdpFailedPendingRsync; + Self::schedule_rsync_for_record( + record, + &mut self.rsync_inflight, + &self.rsync_failure_by_scope, + &mut self.rsync_failure_probe_inflight, + &self.rsync_failure_scope_reachable, + &mut follow_up_tasks, + ); + } + } + Ok(TransportCompletion { + released_requesters: Vec::new(), + follow_up_tasks, + }) + } + ( + RepoDedupKey::RsyncScope { rsync_scope_uri }, + RepoTransportResultKind::Success { .. }, + ) => { + let mut follow_up_tasks = Vec::new(); + let entry = self + .rsync_inflight + .get_mut(rsync_scope_uri) + .ok_or_else(|| format!("rsync transport not found: {rsync_scope_uri}"))?; + entry.state = TransportTaskState::Finished; + entry.finished_at = Some(finished_at); + entry.last_result = Some(result.clone()); + if let Some(failure_scope_uri) = result.rsync_failure_scope_uri.as_ref() { + self.rsync_failure_probe_inflight.remove(failure_scope_uri); + self.rsync_failure_scope_reachable + .insert(failure_scope_uri.clone()); + } + let released_requesters = std::mem::take(&mut entry.waiting_requesters); + for record in self.runtime_records.values_mut() { + if record.rsync_scope_key == *rsync_scope_uri + && matches!( + record.state, + RepoRuntimeState::WaitingRsync + | RepoRuntimeState::RrdpFailedPendingRsync + ) + { + record.state = RepoRuntimeState::RsyncOk; + record.last_success = Some(result.clone()); + } + } + if let Some(failure_scope_uri) = result.rsync_failure_scope_uri.as_ref() { + for record in self.runtime_records.values_mut() { + if record.rsync_scope_key != *rsync_scope_uri + && record.rsync_failure_scope_key.as_deref() + == Some(failure_scope_uri.as_str()) + && matches!(record.state, RepoRuntimeState::WaitingRsync) + { + Self::schedule_rsync_for_record( + record, + &mut self.rsync_inflight, + &self.rsync_failure_by_scope, + &mut self.rsync_failure_probe_inflight, + &self.rsync_failure_scope_reachable, + &mut follow_up_tasks, + ); + } + } + } + Ok(TransportCompletion { + released_requesters, + follow_up_tasks, + }) + } + ( + RepoDedupKey::RsyncScope { rsync_scope_uri }, + RepoTransportResultKind::Failed { .. }, + ) => { + let entry = self + .rsync_inflight + .get_mut(rsync_scope_uri) + .ok_or_else(|| format!("rsync transport not found: {rsync_scope_uri}"))?; + entry.state = TransportTaskState::Finished; + entry.finished_at = Some(finished_at); + entry.last_result = Some(result.clone()); + let reusable_failure_scope = + reusable_rsync_failure_scope(&result).map(str::to_string); + if let Some(failure_scope_uri) = result.rsync_failure_scope_uri.as_ref() { + self.rsync_failure_probe_inflight.remove(failure_scope_uri); + if reusable_failure_scope.as_deref() == Some(failure_scope_uri.as_str()) { + self.rsync_failure_by_scope + .insert(failure_scope_uri.clone(), result.clone()); + } else { + self.rsync_failure_scope_reachable + .insert(failure_scope_uri.clone()); + } + } + let released_requesters = std::mem::take(&mut entry.waiting_requesters); + for record in self.runtime_records.values_mut() { + if (record.rsync_scope_key == *rsync_scope_uri + || reusable_failure_scope + .as_deref() + .map(|scope| record.rsync_failure_scope_key.as_deref() == Some(scope)) + .unwrap_or(false)) + && matches!( + record.state, + RepoRuntimeState::WaitingRsync + | RepoRuntimeState::RrdpFailedPendingRsync + ) + { + record.state = RepoRuntimeState::FailedTerminal; + record.terminal_failure = Some(result.clone()); + } + } + if let Some(failure_scope_uri) = result.rsync_failure_scope_uri.as_ref() + && reusable_failure_scope.as_deref() != Some(failure_scope_uri.as_str()) { + let mut follow_up_tasks = Vec::new(); + for record in self.runtime_records.values_mut() { + if record.rsync_scope_key != *rsync_scope_uri + && record.rsync_failure_scope_key.as_deref() + == Some(failure_scope_uri.as_str()) + && matches!(record.state, RepoRuntimeState::WaitingRsync) + { + Self::schedule_rsync_for_record( + record, + &mut self.rsync_inflight, + &self.rsync_failure_by_scope, + &mut self.rsync_failure_probe_inflight, + &self.rsync_failure_scope_reachable, + &mut follow_up_tasks, + ); + } + } + return Ok(TransportCompletion { + released_requesters, + follow_up_tasks, + }); + } + Ok(TransportCompletion { + released_requesters, + follow_up_tasks: Vec::new(), + }) + } + } + } +} diff --git a/src/scheduler/repo_worker.rs b/src/scheduler/repo_worker.rs new file mode 100644 index 0000000..b41da3c --- /dev/null +++ b/src/scheduler/repo_worker.rs @@ -0,0 +1,30 @@ +use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender}; +use std::sync::{Arc, Mutex}; +use std::thread::{self, JoinHandle}; +use std::time::Duration; + +use super::config::ParallelPhase1Config; +use super::types::{ + RepoSyncResultEnvelope, RepoSyncTask, RepoTransportErrorClass, RepoTransportMode, + RepoTransportResultEnvelope, RepoTransportResultKind, RepoTransportTask, +}; +use crate::output::analysis::timing::TimingHandle; +use crate::output::audit_downloads::DownloadLogHandle; +use crate::repository::current_repo_index::CurrentRepoIndexHandle; +use crate::repository::fetch::rsync::RsyncFetcher; +use crate::repository::fetch::rsync_system::{ + RsyncFailFastProfile, with_scoped_rsync_fail_fast_profile, with_scoped_rsync_timeout_override, +}; +use crate::repository::storage::RocksStore; +use crate::repository::sync::repo::{ + RepoSyncSource, run_rrdp_transport, run_rsync_transport, sync_publication_point, +}; +use crate::repository::sync::rrdp::Fetcher; +use crate::validation::policy::Policy; + +include!("repo_worker/executors.rs"); +include!("repo_worker/pools.rs"); + +#[cfg(test)] +#[path = "repo_worker/tests.rs"] +mod tests; diff --git a/src/scheduler/repo_worker/executors.rs b/src/scheduler/repo_worker/executors.rs new file mode 100644 index 0000000..62c580d --- /dev/null +++ b/src/scheduler/repo_worker/executors.rs @@ -0,0 +1,376 @@ +// Repository sync and transport executor implementations. + +const RETRY_SHORT_TIMEOUT: Duration = Duration::from_secs(1); + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RepoWorkerPoolConfig { + pub max_workers: usize, +} + +impl From<&ParallelPhase1Config> for RepoWorkerPoolConfig { + fn from(value: &ParallelPhase1Config) -> Self { + Self { + max_workers: value.max_repo_sync_workers_global, + } + } +} + +pub trait RepoSyncExecutor: Send + Sync + 'static { + fn execute(&self, task: RepoSyncTask) -> RepoSyncResultEnvelope; +} + +pub trait RepoTransportExecutor: Send + Sync + 'static { + fn execute_transport(&self, task: RepoTransportTask) -> RepoTransportResultEnvelope; +} + +pub struct LiveRrdpTransportExecutor { + store: Arc, + current_repo_index: CurrentRepoIndexHandle, + http_fetcher: Arc, + timing: Option, + download_log: Option, +} + +impl LiveRrdpTransportExecutor { + pub fn new( + store: Arc, + current_repo_index: CurrentRepoIndexHandle, + http_fetcher: Arc, + timing: Option, + download_log: Option, + ) -> Self { + Self { + store, + current_repo_index, + http_fetcher, + timing, + download_log, + } + } +} + +impl RepoTransportExecutor for LiveRrdpTransportExecutor { + fn execute_transport(&self, task: RepoTransportTask) -> RepoTransportResultEnvelope { + let started = std::time::Instant::now(); + debug_assert_eq!(task.mode, RepoTransportMode::Rrdp); + let notification_uri = task + .repo_identity + .notification_uri + .as_deref() + .expect("rrdp transport requires notification uri"); + let sync_result = if task.retry_short_timeout { + crate::repository::fetch::http::with_scoped_http_timeout_override(RETRY_SHORT_TIMEOUT, || { + run_rrdp_transport( + self.store.as_ref(), + notification_uri, + Some(&self.current_repo_index), + self.http_fetcher.as_ref(), + self.timing.as_ref(), + self.download_log.as_ref(), + ) + }) + } else { + run_rrdp_transport( + self.store.as_ref(), + notification_uri, + Some(&self.current_repo_index), + self.http_fetcher.as_ref(), + self.timing.as_ref(), + self.download_log.as_ref(), + ) + }; + match sync_result { + Ok(_) => RepoTransportResultEnvelope { + dedup_key: task.dedup_key, + rsync_failure_scope_uri: task.rsync_failure_scope_uri, + repo_identity: task.repo_identity, + mode: RepoTransportMode::Rrdp, + tal_id: task.tal_id, + rir_id: task.rir_id, + timing_ms: started.elapsed().as_millis() as u64, + result: RepoTransportResultKind::Success { + source: "rrdp".to_string(), + warnings: Vec::new(), + }, + }, + Err(err) => { + RepoTransportResultEnvelope { + dedup_key: task.dedup_key, + rsync_failure_scope_uri: task.rsync_failure_scope_uri, + repo_identity: task.repo_identity, + mode: RepoTransportMode::Rrdp, + tal_id: task.tal_id, + rir_id: task.rir_id, + timing_ms: started.elapsed().as_millis() as u64, + result: RepoTransportResultKind::Failed { + detail: err.to_string(), + warnings: Vec::new(), + error_class: RepoTransportErrorClass::Unknown, + }, + } + } + } + } +} + +pub struct LiveRsyncTransportExecutor { + store: Arc, + current_repo_index: CurrentRepoIndexHandle, + rsync_fetcher: Arc, + timing: Option, + download_log: Option, +} + +impl LiveRsyncTransportExecutor { + pub fn new( + store: Arc, + current_repo_index: CurrentRepoIndexHandle, + rsync_fetcher: Arc, + timing: Option, + download_log: Option, + ) -> Self { + Self { + store, + current_repo_index, + rsync_fetcher, + timing, + download_log, + } + } +} + +impl RepoTransportExecutor for LiveRsyncTransportExecutor { + fn execute_transport(&self, task: RepoTransportTask) -> RepoTransportResultEnvelope { + let started = std::time::Instant::now(); + debug_assert_eq!(task.mode, RepoTransportMode::Rsync); + let sync_result = if task.retry_short_timeout { + with_scoped_rsync_timeout_override(RETRY_SHORT_TIMEOUT, || { + with_scoped_rsync_fail_fast_profile( + RsyncFailFastProfile { + initial_wall_clock_timeout: RETRY_SHORT_TIMEOUT, + max_wall_clock_timeout: RETRY_SHORT_TIMEOUT, + max_attempts: 1, + }, + || { + run_rsync_transport( + self.store.as_ref(), + &task.repo_identity.rsync_base_uri, + Some(&self.current_repo_index), + self.rsync_fetcher.as_ref(), + self.timing.as_ref(), + self.download_log.as_ref(), + ) + }, + ) + }) + } else { + run_rsync_transport( + self.store.as_ref(), + &task.repo_identity.rsync_base_uri, + Some(&self.current_repo_index), + self.rsync_fetcher.as_ref(), + self.timing.as_ref(), + self.download_log.as_ref(), + ) + }; + match sync_result { + Ok(_) => RepoTransportResultEnvelope { + dedup_key: task.dedup_key, + rsync_failure_scope_uri: task.rsync_failure_scope_uri, + repo_identity: task.repo_identity, + mode: RepoTransportMode::Rsync, + tal_id: task.tal_id, + rir_id: task.rir_id, + timing_ms: started.elapsed().as_millis() as u64, + result: RepoTransportResultKind::Success { + source: "rsync".to_string(), + warnings: Vec::new(), + }, + }, + Err(err) => { + RepoTransportResultEnvelope { + dedup_key: task.dedup_key, + rsync_failure_scope_uri: task.rsync_failure_scope_uri, + repo_identity: task.repo_identity, + mode: RepoTransportMode::Rsync, + tal_id: task.tal_id, + rir_id: task.rir_id, + timing_ms: started.elapsed().as_millis() as u64, + result: RepoTransportResultKind::Failed { + detail: err.to_string(), + warnings: Vec::new(), + error_class: RepoTransportErrorClass::Unknown, + }, + } + } + } + } +} + +pub struct LiveRepoTransportExecutor { + rrdp: LiveRrdpTransportExecutor, + rsync: LiveRsyncTransportExecutor, +} + +impl LiveRepoTransportExecutor { + pub fn new( + store: Arc, + current_repo_index: CurrentRepoIndexHandle, + http_fetcher: Arc, + rsync_fetcher: Arc, + timing: Option, + download_log: Option, + ) -> Self { + Self { + rrdp: LiveRrdpTransportExecutor::new( + Arc::clone(&store), + current_repo_index.clone(), + http_fetcher, + timing.clone(), + download_log.clone(), + ), + rsync: LiveRsyncTransportExecutor::new( + store, + current_repo_index, + rsync_fetcher, + timing, + download_log, + ), + } + } +} + +impl RepoTransportExecutor + for LiveRepoTransportExecutor +{ + fn execute_transport(&self, task: RepoTransportTask) -> RepoTransportResultEnvelope { + match task.mode { + RepoTransportMode::Rrdp => self.rrdp.execute_transport(task), + RepoTransportMode::Rsync => self.rsync.execute_transport(task), + } + } +} + +pub struct LiveRepoSyncExecutor { + store: Arc, + policy: Policy, + http_fetcher: Arc, + rsync_fetcher: Arc, + timing: Option, + download_log: Option, +} + +impl LiveRepoSyncExecutor { + pub fn new( + store: Arc, + policy: Policy, + http_fetcher: Arc, + rsync_fetcher: Arc, + timing: Option, + download_log: Option, + ) -> Self { + Self { + store, + policy, + http_fetcher, + rsync_fetcher, + timing, + download_log, + } + } +} + +impl RepoSyncExecutor + for LiveRepoSyncExecutor +{ + fn execute(&self, task: RepoSyncTask) -> RepoSyncResultEnvelope { + let started = std::time::Instant::now(); + crate::logging::progress::emit!( + "phase1_repo_worker_execute_start", + serde_json::json!({ + "repo_key_rsync_base_uri": task.repo_key.rsync_base_uri, + "repo_key_notification_uri": task.repo_key.notification_uri, + "tal_id": task.tal_id, + "rir_id": task.rir_id, + }), + ); + match sync_publication_point( + self.store.as_ref(), + &self.policy, + task.repo_key.notification_uri.as_deref(), + &task.repo_key.rsync_base_uri, + self.http_fetcher.as_ref(), + self.rsync_fetcher.as_ref(), + self.timing.as_ref(), + self.download_log.as_ref(), + ) { + Ok(res) => { + let timing_ms = started.elapsed().as_millis() as u64; + crate::logging::progress::emit!( + "phase1_repo_worker_execute_finish", + serde_json::json!({ + "repo_key_rsync_base_uri": task.repo_key.rsync_base_uri, + "repo_key_notification_uri": task.repo_key.notification_uri, + "result": "success", + "phase": repo_sync_phase_label(res.phase), + "timing_ms": timing_ms, + }), + ); + RepoSyncResultEnvelope { + repo_key: task.repo_key.clone(), + tal_id: task.tal_id, + rir_id: task.rir_id, + result: super::types::RepoSyncResultKind::Success( + super::types::RepoSyncResultRef { + repo_key: task.repo_key, + source: repo_sync_source_label(res.source).to_string(), + }, + ), + phase: Some(repo_sync_phase_label(res.phase).to_string()), + timing_ms, + warnings: res.warnings, + } + } + Err(err) => { + let timing_ms = started.elapsed().as_millis() as u64; + crate::logging::progress::emit!( + "phase1_repo_worker_execute_finish", + serde_json::json!({ + "repo_key_rsync_base_uri": task.repo_key.rsync_base_uri, + "repo_key_notification_uri": task.repo_key.notification_uri, + "result": "failed", + "phase": "repo_sync_failed", + "timing_ms": timing_ms, + "error": err.to_string(), + }), + ); + RepoSyncResultEnvelope { + repo_key: task.repo_key, + tal_id: task.tal_id, + rir_id: task.rir_id, + result: super::types::RepoSyncResultKind::Failed { + detail: err.to_string(), + }, + phase: Some("repo_sync_failed".to_string()), + timing_ms, + warnings: Vec::new(), + } + } + } + } +} + +fn repo_sync_source_label(source: RepoSyncSource) -> &'static str { + match source { + RepoSyncSource::Rrdp => "rrdp", + RepoSyncSource::Rsync => "rsync", + } +} + +fn repo_sync_phase_label(phase: crate::repository::sync::repo::RepoSyncPhase) -> &'static str { + match phase { + crate::repository::sync::repo::RepoSyncPhase::RrdpOk => "rrdp_ok", + crate::repository::sync::repo::RepoSyncPhase::RrdpFailedRsyncOk => "rrdp_failed_rsync_ok", + crate::repository::sync::repo::RepoSyncPhase::RsyncOnlyOk => "rsync_only_ok", + } +} diff --git a/src/scheduler/repo_worker/pools.rs b/src/scheduler/repo_worker/pools.rs new file mode 100644 index 0000000..499071f --- /dev/null +++ b/src/scheduler/repo_worker/pools.rs @@ -0,0 +1,253 @@ +// Worker-pool channels, lifecycle, and worker loops. + +enum RepoWorkerMessage { + Task(RepoSyncTask), + Shutdown, +} + +enum TransportWorkerMessage { + Task(RepoTransportTask), + Shutdown, +} + +pub struct RepoWorkerPool { + config: RepoWorkerPoolConfig, + task_tx: Sender, + result_rx: Receiver, + workers: Vec>, + _executor: Arc, +} + +pub struct RepoTransportWorkerPool { + config: RepoWorkerPoolConfig, + task_tx: Sender, + result_rx: Receiver, + workers: Vec>, + _executor: Arc, +} + +impl RepoWorkerPool { + pub fn new(config: RepoWorkerPoolConfig, executor: E) -> Result { + if config.max_workers == 0 { + return Err("RepoWorkerPool requires at least one worker".to_string()); + } + + let executor = Arc::new(executor); + let (task_tx, task_rx) = mpsc::channel::(); + let (result_tx, result_rx) = mpsc::channel::(); + let shared_task_rx = Arc::new(Mutex::new(task_rx)); + + let mut workers = Vec::with_capacity(config.max_workers); + for idx in 0..config.max_workers { + let task_rx = Arc::clone(&shared_task_rx); + let result_tx = result_tx.clone(); + let executor = Arc::clone(&executor); + workers.push( + thread::Builder::new() + .name(format!("repo-sync-worker-{idx}")) + .spawn(move || worker_loop(task_rx, result_tx, executor)) + .map_err(|e| format!("spawn repo worker failed: {e}"))?, + ); + } + + Ok(Self { + config, + task_tx, + result_rx, + workers, + _executor: executor, + }) + } + + pub fn worker_count(&self) -> usize { + self.config.max_workers + } + + pub fn submit(&self, task: RepoSyncTask) -> Result<(), String> { + self.task_tx + .send(RepoWorkerMessage::Task(task)) + .map_err(|e| format!("submit repo task failed: {e}")) + } + + pub fn recv_result_timeout( + &self, + timeout: Duration, + ) -> Result, String> { + match self.result_rx.recv_timeout(timeout) { + Ok(msg) => Ok(Some(msg)), + Err(RecvTimeoutError::Timeout) => Ok(None), + Err(RecvTimeoutError::Disconnected) => { + Err("repo worker result channel disconnected".to_string()) + } + } + } + + pub fn shutdown(mut self) -> Result<(), String> { + self.shutdown_inner() + } + + fn shutdown_inner(&mut self) -> Result<(), String> { + if self.workers.is_empty() { + return Ok(()); + } + + for _ in 0..self.workers.len() { + self.task_tx + .send(RepoWorkerMessage::Shutdown) + .map_err(|e| format!("send shutdown to repo worker failed: {e}"))?; + } + + let mut first_err: Option = None; + for handle in self.workers.drain(..) { + if let Err(e) = handle.join() + && first_err.is_none() { + first_err = Some(format!("join repo worker failed: {e:?}")); + } + } + + if let Some(err) = first_err { + return Err(err); + } + Ok(()) + } +} + +impl Drop for RepoWorkerPool { + fn drop(&mut self) { + let _ = self.shutdown_inner(); + } +} + +impl RepoTransportWorkerPool { + pub fn new(config: RepoWorkerPoolConfig, executor: E) -> Result { + if config.max_workers == 0 { + return Err("RepoTransportWorkerPool requires at least one worker".to_string()); + } + let executor = Arc::new(executor); + let (task_tx, task_rx) = mpsc::channel::(); + let (result_tx, result_rx) = mpsc::channel::(); + let shared_task_rx = Arc::new(Mutex::new(task_rx)); + let mut workers = Vec::with_capacity(config.max_workers); + for idx in 0..config.max_workers { + let task_rx = Arc::clone(&shared_task_rx); + let result_tx = result_tx.clone(); + let executor = Arc::clone(&executor); + workers.push( + thread::Builder::new() + .name(format!("repo-transport-worker-{idx}")) + .spawn(move || transport_worker_loop(task_rx, result_tx, executor)) + .map_err(|e| format!("spawn repo transport worker failed: {e}"))?, + ); + } + Ok(Self { + config, + task_tx, + result_rx, + workers, + _executor: executor, + }) + } + + pub fn submit(&self, task: RepoTransportTask) -> Result<(), String> { + self.task_tx + .send(TransportWorkerMessage::Task(task)) + .map_err(|e| format!("submit repo transport task failed: {e}")) + } + + pub fn recv_result_timeout( + &self, + timeout: Duration, + ) -> Result, String> { + match self.result_rx.recv_timeout(timeout) { + Ok(msg) => Ok(Some(msg)), + Err(RecvTimeoutError::Timeout) => Ok(None), + Err(RecvTimeoutError::Disconnected) => { + Err("repo transport worker result channel disconnected".to_string()) + } + } + } + + pub fn worker_count(&self) -> usize { + self.config.max_workers + } + + pub fn shutdown(mut self) -> Result<(), String> { + self.shutdown_inner() + } + + fn shutdown_inner(&mut self) -> Result<(), String> { + if self.workers.is_empty() { + return Ok(()); + } + for _ in 0..self.workers.len() { + self.task_tx + .send(TransportWorkerMessage::Shutdown) + .map_err(|e| format!("send shutdown to repo transport worker failed: {e}"))?; + } + let mut first_err: Option = None; + for handle in self.workers.drain(..) { + if let Err(e) = handle.join() + && first_err.is_none() { + first_err = Some(format!("join repo transport worker failed: {e:?}")); + } + } + if let Some(err) = first_err { + return Err(err); + } + Ok(()) + } +} + +impl Drop for RepoTransportWorkerPool { + fn drop(&mut self) { + let _ = self.shutdown_inner(); + } +} + +fn worker_loop( + task_rx: Arc>>, + result_tx: Sender, + executor: Arc, +) { + loop { + let message = { + let rx = task_rx.lock().expect("repo worker receiver lock poisoned"); + rx.recv() + }; + + match message { + Ok(RepoWorkerMessage::Task(task)) => { + let result = executor.execute(task); + if result_tx.send(result).is_err() { + break; + } + } + Ok(RepoWorkerMessage::Shutdown) | Err(_) => break, + } + } +} + +fn transport_worker_loop( + task_rx: Arc>>, + result_tx: Sender, + executor: Arc, +) { + loop { + let message = { + let rx = task_rx + .lock() + .expect("repo transport worker receiver lock poisoned"); + rx.recv() + }; + + match message { + Ok(TransportWorkerMessage::Task(task)) => { + let result = executor.execute_transport(task); + if result_tx.send(result).is_err() { + break; + } + } + Ok(TransportWorkerMessage::Shutdown) | Err(_) => break, + } + } +} diff --git a/src/scheduler/repo_worker/tests.rs b/src/scheduler/repo_worker/tests.rs new file mode 100644 index 0000000..8352851 --- /dev/null +++ b/src/scheduler/repo_worker/tests.rs @@ -0,0 +1,598 @@ +// Repository worker and transport executor tests. + +use base64::Engine; +use sha2::Digest; +use std::collections::HashMap; +use std::fs; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Barrier, Mutex}; +use std::time::Duration; + +use super::{ + LiveRepoSyncExecutor, LiveRrdpTransportExecutor, LiveRsyncTransportExecutor, RepoSyncExecutor, + RepoTransportExecutor, RepoTransportWorkerPool, RepoWorkerPool, RepoWorkerPoolConfig, +}; +use crate::output::report::Warning; +use crate::repository::current_repo_index::CurrentRepoIndex; +use crate::repository::fetch::rsync::{ + LocalDirRsyncFetcher, RsyncFetchError, RsyncFetchResult, RsyncFetcher, +}; +use crate::repository::storage::RocksStore; +use crate::repository::sync::rrdp::Fetcher; +use crate::scheduler::config::ParallelPhase1Config; +use crate::scheduler::types::{ + RepoDedupKey, RepoIdentity, RepoKey, RepoRequester, RepoSyncResultEnvelope, RepoSyncResultKind, + RepoSyncResultRef, RepoSyncTask, RepoTransportMode, RepoTransportResultEnvelope, + RepoTransportResultKind, RepoTransportTask, +}; +use crate::validation::policy::SyncPreference; + +fn sample_task(name: &str) -> RepoSyncTask { + RepoSyncTask { + repo_key: RepoKey::new(format!("rsync://example.test/{name}/"), None), + validation_time: time::OffsetDateTime::UNIX_EPOCH, + sync_preference: SyncPreference::RrdpThenRsync, + tal_id: "arin".to_string(), + rir_id: "arin".to_string(), + priority: 0, + requesters: vec![RepoRequester { + tal_id: "arin".to_string(), + rir_id: "arin".to_string(), + parent_node_id: None, + ca_instance_handle_id: format!("node:{name}"), + publication_point_rsync_uri: format!("rsync://example.test/{name}/"), + manifest_rsync_uri: format!("rsync://example.test/{name}/root.mft"), + }], + } +} + +fn sample_rrdp_transport_task(notification_uri: &str, rsync_base_uri: &str) -> RepoTransportTask { + RepoTransportTask { + dedup_key: RepoDedupKey::RrdpNotify { + notification_uri: notification_uri.to_string(), + }, + rsync_failure_scope_uri: None, + repo_identity: RepoIdentity::new(Some(notification_uri.to_string()), rsync_base_uri), + mode: RepoTransportMode::Rrdp, + retry_short_timeout: false, + tal_id: "arin".to_string(), + rir_id: "arin".to_string(), + validation_time: time::OffsetDateTime::UNIX_EPOCH, + priority: 0, + requesters: vec![RepoRequester::with_tal_rir( + "arin", + "arin", + format!("{rsync_base_uri}root.mft"), + rsync_base_uri.to_string(), + "node:rrdp", + )], + } +} + +fn sample_rsync_transport_task(rsync_scope_uri: &str, rsync_base_uri: &str) -> RepoTransportTask { + RepoTransportTask { + dedup_key: RepoDedupKey::RsyncScope { + rsync_scope_uri: rsync_scope_uri.to_string(), + }, + rsync_failure_scope_uri: None, + repo_identity: RepoIdentity::new(None, rsync_base_uri), + mode: RepoTransportMode::Rsync, + retry_short_timeout: false, + tal_id: "arin".to_string(), + rir_id: "arin".to_string(), + validation_time: time::OffsetDateTime::UNIX_EPOCH, + priority: 0, + requesters: vec![RepoRequester::with_tal_rir( + "arin", + "arin", + format!("{rsync_base_uri}root.mft"), + rsync_base_uri.to_string(), + "node:rsync", + )], + } +} + +struct SuccessExecutor; + +impl RepoSyncExecutor for SuccessExecutor { + fn execute(&self, task: RepoSyncTask) -> RepoSyncResultEnvelope { + RepoSyncResultEnvelope { + repo_key: task.repo_key.clone(), + tal_id: task.tal_id, + rir_id: task.rir_id, + result: RepoSyncResultKind::Success(RepoSyncResultRef { + repo_key: task.repo_key, + source: "rrdp".to_string(), + }), + phase: Some("rrdp_ok".to_string()), + timing_ms: 12, + warnings: Vec::new(), + } + } +} + +struct FailureExecutor; + +impl RepoSyncExecutor for FailureExecutor { + fn execute(&self, task: RepoSyncTask) -> RepoSyncResultEnvelope { + RepoSyncResultEnvelope { + repo_key: task.repo_key, + tal_id: task.tal_id, + rir_id: task.rir_id, + result: RepoSyncResultKind::Failed { + detail: "timeout".to_string(), + }, + phase: Some("repo_sync_failed".to_string()), + timing_ms: 33, + warnings: vec![Warning::new("timeout")], + } + } +} + +struct BarrierExecutor { + barrier: Arc, + active: Arc, + peak: Arc, +} + +#[derive(Clone)] +struct PanicHttpFetcher; + +impl Fetcher for PanicHttpFetcher { + fn fetch(&self, _uri: &str) -> Result, String> { + panic!("http fetch should not be used in this test") + } +} + +#[derive(Clone)] +struct ErrorHttpFetcher; + +impl Fetcher for ErrorHttpFetcher { + fn fetch(&self, uri: &str) -> Result, String> { + Err(format!("fetch blocked for {uri}")) + } +} + +#[derive(Clone)] +struct FailingRsyncFetcher; + +#[derive(Clone, Default)] +struct MockHttpFetcher { + map: Arc>>>, +} + +impl MockHttpFetcher { + fn new() -> Self { + Self::default() + } + + fn insert(&self, uri: &str, bytes: Vec) { + self.map + .lock() + .expect("http fixture lock") + .insert(uri.to_string(), bytes); + } +} + +impl RsyncFetcher for FailingRsyncFetcher { + fn fetch_objects(&self, _rsync_base_uri: &str) -> RsyncFetchResult)>> { + Err(RsyncFetchError::Fetch("boom".to_string())) + } +} + +impl Fetcher for MockHttpFetcher { + fn fetch(&self, uri: &str) -> Result, String> { + self.map + .lock() + .expect("http fixture lock") + .get(uri) + .cloned() + .ok_or_else(|| format!("missing fixture for {uri}")) + } +} + +fn sha256_hex(bytes: &[u8]) -> String { + hex::encode(sha2::Sha256::digest(bytes)) +} + +fn rrdp_notification_xml(session_id: &str, serial: u64, snapshot_uri: &str) -> String { + let snapshot_body = rrdp_snapshot_xml( + session_id, + &[("rsync://example.test/repo/a.roa", b"a".as_ref())], + ); + let snapshot_hash = sha256_hex(snapshot_body.as_bytes()); + format!( + r#" + + +"# + ) +} + +fn rrdp_snapshot_xml(session_id: &str, objects: &[(&str, &[u8])]) -> String { + let mut body = String::from(&format!( + r#" + +"# + )); + for (uri, bytes) in objects { + body.push_str(&format!( + " {}\n", + base64::engine::general_purpose::STANDARD.encode(bytes) + )); + } + body.push_str(""); + body +} + +impl RepoSyncExecutor for BarrierExecutor { + fn execute(&self, task: RepoSyncTask) -> RepoSyncResultEnvelope { + let now = self.active.fetch_add(1, Ordering::SeqCst) + 1; + loop { + let peak = self.peak.load(Ordering::SeqCst); + if now > peak { + if self + .peak + .compare_exchange(peak, now, Ordering::SeqCst, Ordering::SeqCst) + .is_ok() + { + break; + } + } else { + break; + } + } + self.barrier.wait(); + self.active.fetch_sub(1, Ordering::SeqCst); + RepoSyncResultEnvelope { + repo_key: task.repo_key.clone(), + tal_id: task.tal_id, + rir_id: task.rir_id, + result: RepoSyncResultKind::Success(RepoSyncResultRef { + repo_key: task.repo_key, + source: "rsync".to_string(), + }), + phase: Some("rsync_only_ok".to_string()), + timing_ms: 1, + warnings: Vec::new(), + } + } +} + +struct SuccessTransportExecutor; + +impl RepoTransportExecutor for SuccessTransportExecutor { + fn execute_transport(&self, task: RepoTransportTask) -> RepoTransportResultEnvelope { + RepoTransportResultEnvelope { + dedup_key: task.dedup_key, + rsync_failure_scope_uri: task.rsync_failure_scope_uri, + repo_identity: task.repo_identity, + mode: task.mode, + tal_id: task.tal_id, + rir_id: task.rir_id, + timing_ms: 5, + result: RepoTransportResultKind::Success { + source: match task.mode { + RepoTransportMode::Rrdp => "rrdp".to_string(), + RepoTransportMode::Rsync => "rsync".to_string(), + }, + warnings: Vec::new(), + }, + } + } +} + +#[test] +fn repo_worker_pool_config_uses_parallel_phase1_budget() { + let cfg = ParallelPhase1Config { + max_repo_sync_workers_global: 9, + ..ParallelPhase1Config::default() + }; + let pool_cfg = RepoWorkerPoolConfig::from(&cfg); + assert_eq!(pool_cfg.max_workers, 9); +} + +#[test] +fn repo_worker_pool_rejects_zero_workers() { + let err = RepoWorkerPool::new(RepoWorkerPoolConfig { max_workers: 0 }, SuccessExecutor) + .err() + .expect("zero workers should fail"); + assert!(err.contains("at least one worker")); +} + +#[test] +fn repo_worker_pool_processes_tasks_and_returns_results() { + let pool = RepoWorkerPool::new(RepoWorkerPoolConfig { max_workers: 2 }, SuccessExecutor) + .expect("pool"); + pool.submit(sample_task("a")).expect("submit task a"); + pool.submit(sample_task("b")).expect("submit task b"); + + let results = [ + pool.recv_result_timeout(Duration::from_secs(1)) + .expect("recv result 1") + .expect("result 1"), + pool.recv_result_timeout(Duration::from_secs(1)) + .expect("recv result 2") + .expect("result 2"), + ]; + + assert_eq!(results.len(), 2); + assert!( + results + .iter() + .all(|res| matches!(res.result, RepoSyncResultKind::Success(_))) + ); +} + +#[test] +fn repo_worker_pool_returns_failure_envelopes() { + let pool = RepoWorkerPool::new(RepoWorkerPoolConfig { max_workers: 1 }, FailureExecutor) + .expect("pool"); + pool.submit(sample_task("fail")).expect("submit"); + let result = pool + .recv_result_timeout(Duration::from_secs(1)) + .expect("recv") + .expect("result"); + assert!(matches!( + result.result, + RepoSyncResultKind::Failed { ref detail } if detail == "timeout" + )); + assert_eq!(result.warnings, vec![Warning::new("timeout")]); +} + +#[test] +fn repo_worker_pool_processes_work_in_parallel() { + let barrier = Arc::new(Barrier::new(2)); + let active = Arc::new(AtomicUsize::new(0)); + let peak = Arc::new(AtomicUsize::new(0)); + let executor = BarrierExecutor { + barrier: Arc::clone(&barrier), + active: Arc::clone(&active), + peak: Arc::clone(&peak), + }; + let pool = + RepoWorkerPool::new(RepoWorkerPoolConfig { max_workers: 2 }, executor).expect("pool"); + pool.submit(sample_task("a")).expect("submit a"); + pool.submit(sample_task("b")).expect("submit b"); + + let _ = pool + .recv_result_timeout(Duration::from_secs(1)) + .expect("recv 1") + .expect("result 1"); + let _ = pool + .recv_result_timeout(Duration::from_secs(1)) + .expect("recv 2") + .expect("result 2"); + + assert!(peak.load(Ordering::SeqCst) >= 2); +} + +#[test] +fn repo_worker_pool_exposes_configured_worker_count() { + let pool = RepoWorkerPool::new(RepoWorkerPoolConfig { max_workers: 3 }, SuccessExecutor) + .expect("pool"); + assert_eq!(pool.worker_count(), 3); +} + +#[test] +fn repo_transport_worker_pool_processes_transport_tasks() { + let pool = RepoTransportWorkerPool::new( + RepoWorkerPoolConfig { max_workers: 2 }, + SuccessTransportExecutor, + ) + .expect("pool"); + pool.submit(sample_rrdp_transport_task( + "https://example.test/notify.xml", + "rsync://example.test/repo/", + )) + .expect("submit rrdp"); + pool.submit(sample_rsync_transport_task( + "rsync://example.test/module/", + "rsync://example.test/repo/", + )) + .expect("submit rsync"); + + let first = pool + .recv_result_timeout(Duration::from_secs(1)) + .expect("recv first") + .expect("first result"); + let second = pool + .recv_result_timeout(Duration::from_secs(1)) + .expect("recv second") + .expect("second result"); + assert!(matches!( + first.result, + RepoTransportResultKind::Success { .. } + )); + assert!(matches!( + second.result, + RepoTransportResultKind::Success { .. } + )); +} + +#[test] +fn live_repo_sync_executor_runs_rsync_sync_and_updates_store() { + let td = tempfile::tempdir().expect("tempdir"); + fs::create_dir_all(td.path().join("nested")).expect("mkdir"); + fs::write(td.path().join("a.roa"), b"a").expect("write a"); + fs::write(td.path().join("nested").join("b.cer"), b"b").expect("write b"); + + let store_dir = tempfile::tempdir().expect("store tempdir"); + let store = Arc::new(RocksStore::open(store_dir.path()).expect("open store")); + let executor = LiveRepoSyncExecutor::new( + Arc::clone(&store), + crate::validation::policy::Policy::default(), + Arc::new(PanicHttpFetcher), + Arc::new(LocalDirRsyncFetcher::new(td.path())), + None, + None, + ); + + let result = executor.execute(sample_task("repo")); + assert!(matches!(result.result, RepoSyncResultKind::Success(_))); + assert_eq!(result.tal_id, "arin"); + assert_eq!(result.rir_id, "arin"); + assert_eq!(result.warnings.len(), 0); + + let view = store + .get_repository_view_entry("rsync://example.test/repo/a.roa") + .expect("read view") + .expect("entry exists"); + assert_eq!(view.rsync_uri, "rsync://example.test/repo/a.roa"); + assert!(view.current_hash.is_some()); +} + +#[test] +fn live_repo_sync_executor_returns_failure_when_rsync_fails() { + let store_dir = tempfile::tempdir().expect("store tempdir"); + let store = Arc::new(RocksStore::open(store_dir.path()).expect("open store")); + let executor = LiveRepoSyncExecutor::new( + Arc::clone(&store), + crate::validation::policy::Policy::default(), + Arc::new(PanicHttpFetcher), + Arc::new(FailingRsyncFetcher), + None, + None, + ); + + let result = executor.execute(sample_task("repo")); + assert!(matches!( + result.result, + RepoSyncResultKind::Failed { ref detail } if detail.contains("rsync fallback failed") + )); + assert!( + store + .get_repository_view_entry("rsync://example.test/repo/a.roa") + .expect("read view") + .is_none() + ); +} + +#[test] +fn live_rrdp_transport_executor_succeeds_on_valid_notification() { + let td = tempfile::tempdir().expect("tempdir"); + let store = Arc::new(RocksStore::open(td.path()).expect("open store")); + let http = MockHttpFetcher::new(); + let notify = "https://example.test/notification.xml"; + let snapshot = "https://example.test/snapshot.xml"; + let snapshot_bytes = rrdp_snapshot_xml( + "123e4567-e89b-12d3-a456-426614174000", + &[("rsync://example.test/repo/a.roa", b"a".as_ref())], + ); + let notification_bytes = + rrdp_notification_xml("123e4567-e89b-12d3-a456-426614174000", 1, snapshot); + http.insert(notify, notification_bytes.into_bytes()); + http.insert(snapshot, snapshot_bytes.into_bytes()); + let current_repo_index = CurrentRepoIndex::shared(); + let executor = LiveRrdpTransportExecutor::new( + Arc::clone(&store), + current_repo_index.clone(), + Arc::new(http), + None, + None, + ); + let result = executor.execute_transport(sample_rrdp_transport_task( + notify, + "rsync://example.test/repo/", + )); + assert!( + matches!(result.result, RepoTransportResultKind::Success { .. }), + "{result:?}" + ); + let index = current_repo_index.read().expect("index read lock"); + assert!( + index + .get_by_uri("rsync://example.test/repo/a.roa") + .is_some() + ); + assert_eq!( + index.list_scope_uris(notify), + vec!["rsync://example.test/repo/a.roa".to_string()] + ); +} + +#[test] +fn live_rrdp_transport_executor_reports_failure_without_rsync_fallback() { + let td = tempfile::tempdir().expect("tempdir"); + let store = Arc::new(RocksStore::open(td.path()).expect("open store")); + let executor = LiveRrdpTransportExecutor::new( + Arc::clone(&store), + CurrentRepoIndex::shared(), + Arc::new(ErrorHttpFetcher), + None, + None, + ); + let result = executor.execute_transport(sample_rrdp_transport_task( + "https://example.test/notification.xml", + "rsync://example.test/repo/", + )); + assert!(matches!( + result.result, + RepoTransportResultKind::Failed { .. } + )); +} + +#[test] +fn live_rsync_transport_executor_succeeds_on_local_repo() { + let td = tempfile::tempdir().expect("tempdir"); + fs::create_dir_all(td.path().join("nested")).expect("mkdir"); + fs::write(td.path().join("a.roa"), b"a").expect("write"); + fs::write(td.path().join("nested").join("b.cer"), b"b").expect("write"); + let store_dir = tempfile::tempdir().expect("store tempdir"); + let store = Arc::new(RocksStore::open(store_dir.path()).expect("open store")); + let current_repo_index = CurrentRepoIndex::shared(); + let executor = LiveRsyncTransportExecutor::new( + Arc::clone(&store), + current_repo_index.clone(), + Arc::new(LocalDirRsyncFetcher::new(td.path())), + None, + None, + ); + let result = executor.execute_transport(sample_rsync_transport_task( + "rsync://example.test/repo/", + "rsync://example.test/repo/", + )); + assert!(matches!( + result.result, + RepoTransportResultKind::Success { .. } + )); + let index = current_repo_index.read().expect("index read lock"); + assert!( + index + .get_by_uri("rsync://example.test/repo/a.roa") + .is_some() + ); + assert!( + index + .get_by_uri("rsync://example.test/repo/nested/b.cer") + .is_some() + ); + assert_eq!( + index.list_scope_uris("rsync://example.test/repo/"), + vec![ + "rsync://example.test/repo/a.roa".to_string(), + "rsync://example.test/repo/nested/b.cer".to_string() + ] + ); +} + +#[test] +fn live_rsync_transport_executor_reports_failure() { + let store_dir = tempfile::tempdir().expect("store tempdir"); + let store = Arc::new(RocksStore::open(store_dir.path()).expect("open store")); + let executor = LiveRsyncTransportExecutor::new( + Arc::clone(&store), + CurrentRepoIndex::shared(), + Arc::new(FailingRsyncFetcher), + None, + None, + ); + let result = executor.execute_transport(sample_rsync_transport_task( + "rsync://example.test/module/", + "rsync://example.test/repo/", + )); + assert!(matches!( + result.result, + RepoTransportResultKind::Failed { .. } + )); +} diff --git a/src/scheduler/run_coordinator.rs b/src/scheduler/run_coordinator.rs new file mode 100644 index 0000000..0b76cfc --- /dev/null +++ b/src/scheduler/run_coordinator.rs @@ -0,0 +1,234 @@ +#![allow(clippy::too_many_arguments)] + +use std::collections::VecDeque; + +use crate::repository::current_repo_index::{CurrentRepoIndex, CurrentRepoIndexHandle}; +use crate::scheduler::config::ParallelPhase1Config; +use crate::scheduler::repo_scheduler::{ + InFlightRepoTable, RepoCompletion, RepoRequestAction, TransportCompletion, + TransportRequestAction, TransportStateTables, +}; +use crate::scheduler::stats::ParallelRunStats; +use crate::scheduler::types::{ + RepoDedupKey, RepoIdentity, RepoKey, RepoRequester, RepoSyncResultEnvelope, RepoSyncTask, + RepoTransportResultEnvelope, RepoTransportTask, TalInputSpec, +}; +use crate::validation::policy::SyncPreference; + +pub struct GlobalRunCoordinator { + pub config: ParallelPhase1Config, + pub tal_inputs: Vec, + pub current_repo_index: CurrentRepoIndexHandle, + pub in_flight_repos: InFlightRepoTable, + pub transport_tables: TransportStateTables, + pub pending_repo_tasks: VecDeque, + pub pending_transport_tasks: VecDeque, + pub stats: ParallelRunStats, +} + +impl GlobalRunCoordinator { + pub fn new(config: ParallelPhase1Config, tal_inputs: Vec) -> Self { + Self { + config, + tal_inputs, + current_repo_index: CurrentRepoIndex::shared(), + in_flight_repos: InFlightRepoTable::new(), + transport_tables: TransportStateTables::new(), + pending_repo_tasks: VecDeque::new(), + pending_transport_tasks: VecDeque::new(), + stats: ParallelRunStats::default(), + } + } + + pub fn current_repo_index_handle(&self) -> CurrentRepoIndexHandle { + self.current_repo_index.clone() + } + + pub fn register_repo_request( + &mut self, + repo_key: RepoKey, + requester: RepoRequester, + validation_time: time::OffsetDateTime, + sync_preference: SyncPreference, + priority: u8, + ) -> RepoRequestAction { + let action = self.in_flight_repos.register_request( + repo_key, + requester, + validation_time, + sync_preference, + priority, + ); + + match &action { + RepoRequestAction::Enqueued(task) => { + self.stats.repo_tasks_total += 1; + self.pending_repo_tasks.push_back(task.clone()); + self.stats.repo_queue_depth = self.pending_repo_tasks.len(); + } + RepoRequestAction::Reused(_) | RepoRequestAction::FailedReuse { .. } => { + self.stats.repo_tasks_reused += 1; + } + RepoRequestAction::Waiting => {} + } + + action + } + + pub fn pop_next_repo_task(&mut self) -> Option { + let next = self.pending_repo_tasks.pop_front(); + self.stats.repo_queue_depth = self.pending_repo_tasks.len(); + next + } + + pub fn mark_repo_running( + &mut self, + repo_key: &RepoKey, + started_at: time::OffsetDateTime, + ) -> Result<(), String> { + self.in_flight_repos.mark_running(repo_key, started_at)?; + self.stats.repo_tasks_running += 1; + Ok(()) + } + + pub fn complete_repo_success( + &mut self, + result: RepoSyncResultEnvelope, + finished_at: time::OffsetDateTime, + ) -> Result { + let repo_key = result.repo_key.clone(); + let completion = self + .in_flight_repos + .complete_success(&repo_key, result, finished_at)?; + self.stats.repo_tasks_running = self.stats.repo_tasks_running.saturating_sub(1); + Ok(completion) + } + + pub fn complete_repo_failure( + &mut self, + result: RepoSyncResultEnvelope, + finished_at: time::OffsetDateTime, + ) -> Result { + let repo_key = result.repo_key.clone(); + let completion = self + .in_flight_repos + .complete_failure(&repo_key, result, finished_at)?; + self.stats.repo_tasks_running = self.stats.repo_tasks_running.saturating_sub(1); + self.stats.repo_tasks_failed += 1; + Ok(completion) + } + + pub fn register_transport_request( + &mut self, + identity: RepoIdentity, + requester: RepoRequester, + validation_time: time::OffsetDateTime, + priority: u8, + rsync_scope_uri: String, + rsync_failure_scope_uri: Option, + sync_preference: SyncPreference, + retry_short_timeout: bool, + ) -> TransportRequestAction { + let mut action = self.transport_tables.register_transport_request( + identity, + requester, + validation_time, + priority, + rsync_scope_uri, + rsync_failure_scope_uri, + sync_preference, + ); + match &mut action { + TransportRequestAction::Enqueue(task) => { + if retry_short_timeout { + task.retry_short_timeout = true; + } + self.stats.repo_tasks_total += 1; + self.pending_transport_tasks.push_back(task.clone()); + self.stats.repo_queue_depth = self.pending_transport_tasks.len(); + } + TransportRequestAction::ReusedSuccess(_) + | TransportRequestAction::ReusedTerminalFailure(_) => { + self.stats.repo_tasks_reused += 1; + } + TransportRequestAction::Waiting { .. } => {} + } + action + } + + pub fn push_transport_task(&mut self, task: RepoTransportTask) { + self.stats.repo_tasks_total += 1; + self.pending_transport_tasks.push_back(task); + self.stats.repo_queue_depth = self.pending_transport_tasks.len(); + } + + pub fn pop_next_transport_task(&mut self) -> Option { + let next = self.pending_transport_tasks.pop_front(); + self.stats.repo_queue_depth = self.pending_transport_tasks.len(); + next + } + + pub fn mark_transport_running( + &mut self, + dedup_key: &crate::scheduler::types::RepoDedupKey, + started_at: time::OffsetDateTime, + ) -> Result<(), String> { + self.transport_tables + .mark_transport_running(dedup_key, started_at)?; + self.stats.repo_tasks_running += 1; + Ok(()) + } + + pub fn complete_transport_result( + &mut self, + result: RepoTransportResultEnvelope, + finished_at: time::OffsetDateTime, + ) -> Result { + let completion = self + .transport_tables + .complete_transport_result(result.clone(), finished_at)?; + self.stats.repo_tasks_running = self.stats.repo_tasks_running.saturating_sub(1); + if matches!( + result.result, + crate::scheduler::types::RepoTransportResultKind::Failed { .. } + ) && result.mode == crate::scheduler::types::RepoTransportMode::Rsync + { + self.stats.repo_tasks_failed += 1; + } + Ok(completion) + } + + pub fn runtime_record( + &self, + identity: &RepoIdentity, + ) -> Option<&crate::scheduler::repo_scheduler::RepoRuntimeRecord> { + self.transport_tables.runtime_record(identity) + } + + pub fn finalized_runtime_records_for_transport( + &self, + dedup_key: &RepoDedupKey, + ) -> Vec { + self.transport_tables + .finalized_runtime_records_for_transport(dedup_key) + } + + pub fn finalized_runtime_records_for_transport_result( + &self, + result: &RepoTransportResultEnvelope, + ) -> Vec { + self.transport_tables + .finalized_runtime_records_for_transport_result(result) + } + + pub fn reset_run_state(&mut self) { + self.in_flight_repos.reset_run_state(); + self.transport_tables.reset_run_state(); + self.pending_repo_tasks.clear(); + self.pending_transport_tasks.clear(); + self.stats = ParallelRunStats::default(); + if let Ok(mut index) = self.current_repo_index.write() { + index.clear(); + } + } +} diff --git a/src/scheduler/stats.rs b/src/scheduler/stats.rs new file mode 100644 index 0000000..157539a --- /dev/null +++ b/src/scheduler/stats.rs @@ -0,0 +1,25 @@ +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct ParallelRunStats { + pub repo_tasks_total: usize, + pub repo_tasks_reused: usize, + pub repo_tasks_running: usize, + pub repo_tasks_failed: usize, + pub inflight_snapshot_bytes: usize, + pub repo_queue_depth: usize, +} + +#[cfg(test)] +mod tests { + use super::ParallelRunStats; + + #[test] + fn parallel_run_stats_default_to_zero() { + let stats = ParallelRunStats::default(); + assert_eq!(stats.repo_tasks_total, 0); + assert_eq!(stats.repo_tasks_reused, 0); + assert_eq!(stats.repo_tasks_running, 0); + assert_eq!(stats.repo_tasks_failed, 0); + assert_eq!(stats.inflight_snapshot_bytes, 0); + assert_eq!(stats.repo_queue_depth, 0); + } +} diff --git a/src/scheduler/types.rs b/src/scheduler/types.rs new file mode 100644 index 0000000..24f26aa --- /dev/null +++ b/src/scheduler/types.rs @@ -0,0 +1,533 @@ +use std::path::{Path, PathBuf}; + +use serde::{Deserialize, Serialize}; + +use crate::output::report::Warning; +use crate::validation::policy::SyncPreference; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum TalSource { + Url(String), + DerBytes { + tal_url: String, + tal_bytes: Vec, + ta_der: Vec, + }, + FilePath(PathBuf), + FilePathWithTa { + tal_path: PathBuf, + ta_path: PathBuf, + }, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TalInputSpec { + pub tal_id: String, + pub rir_id: String, + pub source: TalSource, +} + +impl TalInputSpec { + pub fn from_url(url: impl Into) -> Self { + let url = url.into(); + let tal_id = derive_tal_id_from_url_like(&url); + Self { + rir_id: tal_id.clone(), + tal_id, + source: TalSource::Url(url), + } + } + + pub fn from_file_path(path: impl Into) -> Self { + let path = path.into(); + let tal_id = derive_tal_id_from_path(&path); + Self { + rir_id: tal_id.clone(), + tal_id, + source: TalSource::FilePath(path), + } + } + + pub fn from_file_path_with_ta( + tal_path: impl Into, + ta_path: impl Into, + ) -> Self { + let tal_path = tal_path.into(); + let ta_path = ta_path.into(); + let tal_id = derive_tal_id_from_path(&tal_path); + Self { + rir_id: tal_id.clone(), + tal_id, + source: TalSource::FilePathWithTa { tal_path, ta_path }, + } + } + + pub fn from_ta_der(tal_url: impl Into, tal_bytes: Vec, ta_der: Vec) -> Self { + let tal_url = tal_url.into(); + let tal_id = derive_tal_id_from_url_like(&tal_url); + Self { + rir_id: tal_id.clone(), + tal_id, + source: TalSource::DerBytes { + tal_url, + tal_bytes, + ta_der, + }, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct RepoIdentity { + pub notification_uri: Option, + pub rsync_base_uri: String, +} + +impl RepoIdentity { + pub fn new(notification_uri: Option, rsync_base_uri: impl Into) -> Self { + Self { + notification_uri, + rsync_base_uri: rsync_base_uri.into(), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum RepoDedupKey { + RrdpNotify { notification_uri: String }, + RsyncScope { rsync_scope_uri: String }, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum RepoTransportMode { + Rrdp, + Rsync, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RepoTransportTask { + pub dedup_key: RepoDedupKey, + pub rsync_failure_scope_uri: Option, + pub repo_identity: RepoIdentity, + pub mode: RepoTransportMode, + pub retry_short_timeout: bool, + pub tal_id: String, + pub rir_id: String, + pub validation_time: time::OffsetDateTime, + pub priority: u8, + pub requesters: Vec, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[derive(Default)] +pub enum RepoTransportErrorClass { + /// Transport-layer fetch failure (connect timeout/refused/TLS/DNS/broken + /// connection). This class is retained for transport diagnostics. + TransportFetch, + /// Protocol-level failure (HTTP status, XML/parse, content mismatch). + Protocol, + /// Local storage failure. + Storage, + /// Unclassified (synthesized or legacy results); never counted. + #[default] + Unknown, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RepoTransportResultKind { + Success { + source: String, + warnings: Vec, + }, + Failed { + detail: String, + warnings: Vec, + error_class: RepoTransportErrorClass, + }, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RepoTransportResultEnvelope { + pub dedup_key: RepoDedupKey, + pub rsync_failure_scope_uri: Option, + pub repo_identity: RepoIdentity, + pub mode: RepoTransportMode, + pub tal_id: String, + pub rir_id: String, + pub timing_ms: u64, + pub result: RepoTransportResultKind, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RepoRuntimeState { + Init, + WaitingRrdp, + RrdpOk, + RrdpFailedPendingRsync, + WaitingRsync, + RsyncOk, + FailedTerminal, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct RepoKey { + pub rsync_base_uri: String, + pub notification_uri: Option, +} + +impl RepoKey { + pub fn new(rsync_base_uri: impl Into, notification_uri: Option) -> Self { + Self { + rsync_base_uri: rsync_base_uri.into(), + notification_uri, + } + } + + pub fn as_identity(&self) -> RepoIdentity { + RepoIdentity { + notification_uri: self.notification_uri.clone(), + rsync_base_uri: self.rsync_base_uri.clone(), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RepoRequester { + pub tal_id: String, + pub rir_id: String, + pub parent_node_id: Option, + pub ca_instance_handle_id: String, + pub publication_point_rsync_uri: String, + pub manifest_rsync_uri: String, +} + +impl RepoRequester { + pub fn with_tal_rir( + tal_id: impl Into, + rir_id: impl Into, + manifest_rsync_uri: impl Into, + publication_point_rsync_uri: impl Into, + ca_instance_handle_id: impl Into, + ) -> Self { + Self { + tal_id: tal_id.into(), + rir_id: rir_id.into(), + parent_node_id: None, + ca_instance_handle_id: ca_instance_handle_id.into(), + publication_point_rsync_uri: publication_point_rsync_uri.into(), + manifest_rsync_uri: manifest_rsync_uri.into(), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RepoSyncTask { + pub repo_key: RepoKey, + pub validation_time: time::OffsetDateTime, + pub sync_preference: SyncPreference, + pub tal_id: String, + pub rir_id: String, + pub priority: u8, + pub requesters: Vec, +} + +impl RepoSyncTask { + pub fn as_transport_task( + &self, + dedup_key: RepoDedupKey, + mode: RepoTransportMode, + ) -> RepoTransportTask { + RepoTransportTask { + dedup_key, + rsync_failure_scope_uri: None, + repo_identity: self.repo_key.as_identity(), + mode, + retry_short_timeout: false, + tal_id: self.tal_id.clone(), + rir_id: self.rir_id.clone(), + validation_time: self.validation_time, + priority: self.priority, + requesters: self.requesters.clone(), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RepoTaskState { + Pending, + Running, + Succeeded, + Failed, + Reused, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RepoSyncResultRef { + pub repo_key: RepoKey, + pub source: String, +} + +impl RepoSyncResultRef { + pub fn as_identity(&self) -> RepoIdentity { + self.repo_key.as_identity() + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct InFlightRepoEntry { + pub state: RepoTaskState, + pub task_ref: Option, + pub waiting_requesters: Vec, + pub result_ref: Option, + pub last_result: Option, + pub last_error: Option, + pub started_at: Option, + pub finished_at: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RepoSyncResultEnvelope { + pub repo_key: RepoKey, + pub tal_id: String, + pub rir_id: String, + pub result: RepoSyncResultKind, + pub phase: Option, + pub timing_ms: u64, + pub warnings: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RepoSyncResultKind { + Success(RepoSyncResultRef), + Failed { detail: String }, + Reused(RepoSyncResultRef), +} + +fn derive_tal_id_from_url_like(s: &str) -> String { + if let Ok(url) = url::Url::parse(s) { + if let Some(last) = url + .path_segments() + .and_then(|mut segments| segments.rfind(|seg| !seg.is_empty())) + { + let stem = last.rsplit_once('.').map(|(stem, _)| stem).unwrap_or(last); + let trimmed = stem.trim(); + if !trimmed.is_empty() { + return trimmed.to_string(); + } + } + if let Some(host) = url.host_str() { + return host.to_string(); + } + } + "unknown-tal".to_string() +} + +fn derive_tal_id_from_path(path: &Path) -> String { + path.file_stem() + .and_then(|stem| stem.to_str()) + .map(|s| s.trim()) + .filter(|s| !s.is_empty()) + .unwrap_or("unknown-tal") + .to_string() +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use crate::output::report::Warning; + use crate::validation::policy::SyncPreference; + + use super::{ + RepoDedupKey, RepoIdentity, RepoKey, RepoRequester, RepoRuntimeState, RepoSyncTask, + RepoTaskState, RepoTransportMode, RepoTransportResultEnvelope, RepoTransportResultKind, + TalInputSpec, TalSource, derive_tal_id_from_path, derive_tal_id_from_url_like, + }; + + #[test] + fn tal_input_spec_from_url_derives_tal_and_rir_ids() { + let spec = TalInputSpec::from_url("https://example.test/tals/apnic.tal"); + assert_eq!(spec.tal_id, "apnic"); + assert_eq!(spec.rir_id, "apnic"); + assert_eq!( + spec.source, + TalSource::Url("https://example.test/tals/apnic.tal".to_string()) + ); + } + + #[test] + fn tal_input_spec_from_file_path_derives_file_stem() { + let spec = TalInputSpec::from_file_path("local/arin.tal"); + assert_eq!(spec.tal_id, "arin"); + assert_eq!(spec.rir_id, "arin"); + } + + #[test] + fn tal_input_spec_from_ta_der_preserves_payload() { + let spec = TalInputSpec::from_ta_der( + "https://example.test/ripe.tal", + vec![4, 5, 6], + vec![1, 2, 3], + ); + assert_eq!(spec.tal_id, "ripe"); + assert_eq!(spec.rir_id, "ripe"); + assert_eq!( + spec.source, + TalSource::DerBytes { + tal_url: "https://example.test/ripe.tal".to_string(), + tal_bytes: vec![4, 5, 6], + ta_der: vec![1, 2, 3], + } + ); + } + + #[test] + fn repo_key_equality_uses_rsync_base_and_notification() { + let a = RepoKey::new( + "rsync://example.test/repo/", + Some("https://example.test/notify.xml".to_string()), + ); + let b = RepoKey::new( + "rsync://example.test/repo/", + Some("https://example.test/notify.xml".to_string()), + ); + let c = RepoKey::new("rsync://example.test/repo/", None); + assert_eq!(a, b); + assert_ne!(a, c); + } + + #[test] + fn repo_task_state_variants_are_distinct() { + assert_ne!(RepoTaskState::Pending, RepoTaskState::Running); + assert_ne!(RepoTaskState::Succeeded, RepoTaskState::Failed); + assert_ne!(RepoTaskState::Failed, RepoTaskState::Reused); + } + + #[test] + fn repo_identity_preserves_raw_inputs() { + let ident = RepoIdentity::new( + Some("https://example.test/notify.xml".to_string()), + "rsync://example.test/repo/", + ); + assert_eq!( + ident.notification_uri.as_deref(), + Some("https://example.test/notify.xml") + ); + assert_eq!(ident.rsync_base_uri, "rsync://example.test/repo/"); + } + + #[test] + fn repo_key_can_be_viewed_as_repo_identity() { + let key = RepoKey::new( + "rsync://example.test/repo/", + Some("https://example.test/notify.xml".to_string()), + ); + let ident = key.as_identity(); + assert_eq!(ident.rsync_base_uri, "rsync://example.test/repo/"); + assert_eq!( + ident.notification_uri.as_deref(), + Some("https://example.test/notify.xml") + ); + } + + #[test] + fn repo_sync_task_maps_to_rrdp_transport_task() { + let task = RepoSyncTask { + repo_key: RepoKey::new( + "rsync://example.test/repo/", + Some("https://example.test/notify.xml".to_string()), + ), + validation_time: time::OffsetDateTime::UNIX_EPOCH, + sync_preference: SyncPreference::RrdpThenRsync, + tal_id: "apnic".to_string(), + rir_id: "apnic".to_string(), + priority: 1, + requesters: vec![RepoRequester::with_tal_rir( + "apnic", + "apnic", + "rsync://example.test/repo/root.mft", + "rsync://example.test/repo/", + "node:1", + )], + }; + let transport = task.as_transport_task( + RepoDedupKey::RrdpNotify { + notification_uri: "https://example.test/notify.xml".to_string(), + }, + RepoTransportMode::Rrdp, + ); + assert_eq!(transport.mode, RepoTransportMode::Rrdp); + assert_eq!(transport.tal_id, "apnic"); + assert_eq!(transport.rir_id, "apnic"); + assert_eq!(transport.requesters.len(), 1); + assert_eq!( + transport.repo_identity.notification_uri.as_deref(), + Some("https://example.test/notify.xml") + ); + } + + #[test] + fn repo_transport_result_envelope_supports_success_and_failure_shapes() { + let identity = RepoIdentity::new(None, "rsync://example.test/repo/"); + let ok = RepoTransportResultEnvelope { + dedup_key: RepoDedupKey::RsyncScope { + rsync_scope_uri: "rsync://example.test/module/".to_string(), + }, + rsync_failure_scope_uri: None, + repo_identity: identity.clone(), + mode: RepoTransportMode::Rsync, + tal_id: "arin".to_string(), + rir_id: "arin".to_string(), + timing_ms: 12, + result: RepoTransportResultKind::Success { + source: "rsync".to_string(), + warnings: vec![Warning::new("ok")], + }, + }; + let fail = RepoTransportResultEnvelope { + dedup_key: RepoDedupKey::RsyncScope { + rsync_scope_uri: "rsync://example.test/module/".to_string(), + }, + rsync_failure_scope_uri: None, + repo_identity: identity, + mode: RepoTransportMode::Rsync, + tal_id: "arin".to_string(), + rir_id: "arin".to_string(), + timing_ms: 30, + result: RepoTransportResultKind::Failed { + detail: "timeout".to_string(), + warnings: vec![Warning::new("timeout")], + error_class: super::RepoTransportErrorClass::Unknown, + }, + }; + assert!(matches!(ok.result, RepoTransportResultKind::Success { .. })); + assert!(matches!( + fail.result, + RepoTransportResultKind::Failed { .. } + )); + } + + #[test] + fn repo_runtime_state_variants_are_distinct() { + assert_ne!(RepoRuntimeState::Init, RepoRuntimeState::WaitingRrdp); + assert_ne!(RepoRuntimeState::RrdpOk, RepoRuntimeState::RsyncOk); + assert_ne!( + RepoRuntimeState::RrdpFailedPendingRsync, + RepoRuntimeState::FailedTerminal + ); + } + + #[test] + fn derive_tal_id_helpers_fall_back_safely() { + assert_eq!( + derive_tal_id_from_url_like("https://example.test/path/afrinic.tal"), + "afrinic" + ); + assert_eq!( + derive_tal_id_from_path(Path::new("foo/lacnic.tal")), + "lacnic" + ); + } +} diff --git a/src/ta_constraints.rs b/src/ta_constraints.rs new file mode 100644 index 0000000..6abe861 --- /dev/null +++ b/src/ta_constraints.rs @@ -0,0 +1,9 @@ +//! Locally configured constraints for the resources carried by RPKI EE +//! certificates. The configuration format follows +//! draft-ietf-sidrops-constraining-rpki-trust-anchors. + +include!("ta_constraints/implementation.rs"); + +#[cfg(test)] +#[path = "ta_constraints/tests.rs"] +mod tests; diff --git a/src/ta_constraints/implementation.rs b/src/ta_constraints/implementation.rs new file mode 100644 index 0000000..0bdd192 --- /dev/null +++ b/src/ta_constraints/implementation.rs @@ -0,0 +1,688 @@ +// Interval indexes and trust-anchor resource constraint evaluation. + + +use std::collections::{BTreeMap, BTreeSet}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::ops::Deref; +use std::path::{Path, PathBuf}; + +use crate::model::rc::{ + Afi, AsIdOrRange, AsIdentifierChoice, IpAddressChoice, IpAddressOrRange, IpResourceSet, + ResourceCertificate, +}; +use crate::scheduler::types::{TalInputSpec, TalSource}; + +const LINEAR_INTERVAL_THRESHOLD: usize = 10; + +/// A normalized rule set with a small-set linear path and a large-set tree path. +/// +/// The parser constructs this only after sorting and merging overlapping or +/// adjacent intervals. The tree lookup therefore only needs to inspect the +/// predecessor of a target interval; normalized intervals are disjoint and +/// sorted by their lower bound. +#[derive(Clone)] +struct IntervalIndex { + rules: Vec, + lookup: IntervalLookup, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +enum IntervalLookup { + Linear, + Tree(BTreeMap), +} + +impl std::fmt::Debug for IntervalIndex { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Keep derived acceleration data out of Debug so diagnostics continue + // to describe canonical rules rather than their index layout. + self.rules.fmt(formatter) + } +} + +impl PartialEq for IntervalIndex { + fn eq(&self, other: &Self) -> bool { + self.rules == other.rules + } +} + +impl Eq for IntervalIndex {} + +impl Deref for IntervalIndex { + type Target = [I]; + + fn deref(&self) -> &Self::Target { + &self.rules + } +} + +trait IntervalValue { + fn start(&self) -> u128; + fn end(&self) -> u128; + fn overlaps(&self, other: &Self) -> bool; +} + +impl IntervalIndex { + fn new(rules: Vec) -> Self { + debug_assert!( + rules + .windows(2) + .all(|window| { window[0].end().saturating_add(1) < window[1].start() }) + ); + let lookup = if rules.len() > LINEAR_INTERVAL_THRESHOLD { + let tree = rules + .iter() + .map(|rule| (rule.start(), rule.end())) + .collect(); + IntervalLookup::Tree(tree) + } else { + IntervalLookup::Linear + }; + Self { rules, lookup } + } + + fn any_overlaps(&self, target: &I) -> bool { + match &self.lookup { + IntervalLookup::Linear => self.rules.iter().any(|entry| entry.overlaps(target)), + IntervalLookup::Tree(tree) => tree + .range(..=target.end()) + .next_back() + .map(|(_, end)| *end >= target.start()) + .unwrap_or(false), + } + } + + fn fully_covers(&self, target: &I) -> bool { + match &self.lookup { + IntervalLookup::Linear => self.fully_covers_linear(target), + IntervalLookup::Tree(tree) => tree + .range(..=target.start()) + .next_back() + .map(|(_, end)| *end >= target.end()) + .unwrap_or(false), + } + } + + fn fully_covers_linear(&self, target: &I) -> bool { + let mut cursor = target.start(); + for entry in &self.rules { + if entry.end() < cursor { + continue; + } + if entry.start() > cursor { + return false; + } + if entry.end() >= target.end() { + return true; + } + cursor = entry.end().saturating_add(1); + } + false + } + + #[cfg(test)] + fn uses_tree(&self) -> bool { + matches!(self.lookup, IntervalLookup::Tree(_)) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct TaConstraintsByTal { + by_tal_id: BTreeMap>, +} + +impl TaConstraintsByTal { + pub fn load_for_tals( + tal_inputs: &[TalInputSpec], + explicit_specs: &[String], + ) -> Result { + let tal_ids = tal_inputs + .iter() + .map(|input| input.tal_id.as_str()) + .collect::>(); + let mut explicit_paths = BTreeMap::::new(); + for spec in explicit_specs { + let (tal_id, path) = spec + .split_once('=') + .ok_or_else(|| format!("--ta-constraints expects =, got '{spec}'"))?; + let tal_id = tal_id.trim(); + let path = path.trim(); + if tal_id.is_empty() || path.is_empty() { + return Err(format!( + "--ta-constraints expects non-empty =, got '{spec}'" + )); + } + if !tal_ids.contains(tal_id) { + return Err(format!( + "--ta-constraints references unknown TAL id '{tal_id}'" + )); + } + if explicit_paths + .insert(tal_id.to_string(), PathBuf::from(path)) + .is_some() + { + return Err(format!( + "--ta-constraints specifies TAL id '{tal_id}' more than once" + )); + } + } + + let mut by_tal_id = BTreeMap::new(); + for input in tal_inputs { + let path = explicit_paths + .get(&input.tal_id) + .cloned() + .or_else(|| adjacent_constraints_path(&input.source).filter(|path| path.is_file())); + if let Some(path) = path { + let constraints = TaConstraints::from_file(&path).map_err(|error| { + format!( + "load TA constraints for '{}' from {} failed: {error}", + input.tal_id, + path.display() + ) + })?; + by_tal_id.insert(input.tal_id.clone(), std::sync::Arc::new(constraints)); + } + } + Ok(Self { by_tal_id }) + } + + pub fn for_tal(&self, tal_id: &str) -> Option<&TaConstraints> { + self.by_tal_id.get(tal_id).map(std::sync::Arc::as_ref) + } + + /// Return the immutable, process-local snapshot for a TAL without cloning + /// the rule trees. Phase-2 object workers own this `Arc` in their task + /// payload, while scoped stage workers borrow the same policy map. + pub(crate) fn shared_for_tal(&self, tal_id: &str) -> Option> { + self.by_tal_id.get(tal_id).cloned() + } + + pub fn is_empty(&self) -> bool { + self.by_tal_id.is_empty() + } + + pub fn configuration_warnings(&self) -> Vec { + self.by_tal_id + .iter() + .flat_map(|(tal_id, constraints)| { + constraints.warnings().iter().map(move |warning| { + format!( + "TA constraints for TAL '{tal_id}' ({}): {warning}", + constraints.source().display() + ) + }) + }) + .collect() + } + +} + +fn adjacent_constraints_path(source: &TalSource) -> Option { + match source { + TalSource::FilePath(path) => Some(path.with_extension("constraints")), + TalSource::FilePathWithTa { tal_path, .. } => Some(tal_path.with_extension("constraints")), + TalSource::Url(_) | TalSource::DerBytes { .. } => None, + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TaConstraints { + source: PathBuf, + allow_v4: IntervalIndex, + deny_v4: IntervalIndex, + allow_v6: IntervalIndex, + deny_v6: IntervalIndex, + allow_asn: IntervalIndex, + deny_asn: IntervalIndex, + warnings: Vec, +} + +impl TaConstraints { + pub fn from_file(path: &Path) -> Result { + let contents = std::fs::read_to_string(path) + .map_err(|error| format!("read {}: {error}", path.display()))?; + Self::parse(path.to_path_buf(), &contents) + } + + pub fn warnings(&self) -> &[String] { + &self.warnings + } + + pub fn source(&self) -> &Path { + &self.source + } + + pub fn validate_ee_certificate( + &self, + certificate: &ResourceCertificate, + ) -> Result<(), TaConstraintsViolation> { + if let Some(ip_resources) = certificate.tbs.extensions.ip_resources.as_ref() { + self.validate_ip_resources(ip_resources)?; + } + if let Some(as_resources) = certificate.tbs.extensions.as_resources.as_ref() { + self.validate_as_choice("AS", as_resources.asnum.as_ref())?; + self.validate_as_choice("RDI", as_resources.rdi.as_ref())?; + } + Ok(()) + } + + fn parse(source: PathBuf, contents: &str) -> Result { + let mut allow_v4 = Vec::new(); + let mut deny_v4 = Vec::new(); + let mut allow_v6 = Vec::new(); + let mut deny_v6 = Vec::new(); + let mut allow_asn = Vec::new(); + let mut deny_asn = Vec::new(); + + for (index, raw_line) in contents.lines().enumerate() { + let line_number = index + 1; + let line = raw_line.split('#').next().unwrap_or("").trim(); + if line.is_empty() { + continue; + } + let mut words = line.split_whitespace(); + let action = words.next().expect("non-empty line has first word"); + let resource = words.collect::>().join(" "); + if resource.is_empty() { + return Err(format!( + "line {line_number}: missing resource after '{action}'" + )); + } + let allow = match action { + "allow" => true, + "deny" => false, + _ => { + return Err(format!( + "line {line_number}: expected 'allow' or 'deny', got '{action}'" + )); + } + }; + if looks_like_ip_resource(&resource) { + let interval = parse_ip_interval(&resource).map_err(|error| { + format!("line {line_number}: invalid IP resource '{resource}': {error}") + })?; + match (interval.afi, allow) { + (Afi::Ipv4, true) => allow_v4.push(interval), + (Afi::Ipv4, false) => deny_v4.push(interval), + (Afi::Ipv6, true) => allow_v6.push(interval), + (Afi::Ipv6, false) => deny_v6.push(interval), + } + } else { + let interval = parse_as_interval(&resource).map_err(|error| { + format!("line {line_number}: invalid AS resource '{resource}': {error}") + })?; + if allow { + allow_asn.push(interval); + } else { + deny_asn.push(interval); + } + } + } + + let mut warnings = Vec::new(); + normalize_ip_intervals("allow IPv4", &mut allow_v4, &mut warnings); + normalize_ip_intervals("deny IPv4", &mut deny_v4, &mut warnings); + normalize_ip_intervals("allow IPv6", &mut allow_v6, &mut warnings); + normalize_ip_intervals("deny IPv6", &mut deny_v6, &mut warnings); + normalize_as_intervals("allow AS", &mut allow_asn, &mut warnings); + normalize_as_intervals("deny AS", &mut deny_asn, &mut warnings); + + Ok(Self { + source, + allow_v4: IntervalIndex::new(allow_v4), + deny_v4: IntervalIndex::new(deny_v4), + allow_v6: IntervalIndex::new(allow_v6), + deny_v6: IntervalIndex::new(deny_v6), + allow_asn: IntervalIndex::new(allow_asn), + deny_asn: IntervalIndex::new(deny_asn), + warnings, + }) + } + + fn validate_ip_resources( + &self, + resources: &IpResourceSet, + ) -> Result<(), TaConstraintsViolation> { + for family in &resources.families { + let items = match &family.choice { + // Constraints apply to explicit INR listings. EE profiles for + // constrained signed objects already reject inappropriate inherit. + IpAddressChoice::Inherit => continue, + IpAddressChoice::AddressesOrRanges(items) => items, + }; + let (allow, deny) = match family.afi { + Afi::Ipv4 => (&self.allow_v4, &self.deny_v4), + Afi::Ipv6 => (&self.allow_v6, &self.deny_v6), + }; + for item in items { + let interval = ip_item_to_interval(family.afi, item)?; + if deny.any_overlaps(&interval) { + return Err(TaConstraintsViolation(format!( + "{} {} intersects a deny rule in {}", + afi_name(family.afi), + interval, + self.source.display() + ))); + } + if !allow.fully_covers(&interval) { + return Err(TaConstraintsViolation(format!( + "{} {} is not fully contained in allow rules in {}", + afi_name(family.afi), + interval, + self.source.display() + ))); + } + } + } + Ok(()) + } + + fn validate_as_choice( + &self, + kind: &str, + choice: Option<&AsIdentifierChoice>, + ) -> Result<(), TaConstraintsViolation> { + let Some(choice) = choice else { + return Ok(()); + }; + let items = match choice { + AsIdentifierChoice::Inherit => return Ok(()), + AsIdentifierChoice::AsIdsOrRanges(items) => items, + }; + for item in items { + let interval = match item { + AsIdOrRange::Id(value) => AsInterval::new(*value, *value), + AsIdOrRange::Range { min, max } => AsInterval::new(*min, *max), + }; + if self.deny_asn.any_overlaps(&interval) { + return Err(TaConstraintsViolation(format!( + "{kind} {interval} intersects a deny rule in {}", + self.source.display() + ))); + } + if !self.allow_asn.fully_covers(&interval) { + return Err(TaConstraintsViolation(format!( + "{kind} {interval} is not fully contained in allow rules in {}", + self.source.display() + ))); + } + } + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TaConstraintsViolation(pub String); + +impl std::fmt::Display for TaConstraintsViolation { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(formatter) + } +} + +impl std::error::Error for TaConstraintsViolation {} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct IpInterval { + afi: Afi, + min: u128, + max: u128, +} + +impl IpInterval { + fn new(afi: Afi, min: u128, max: u128) -> Self { + Self { afi, min, max } + } + + fn overlaps(&self, other: &Self) -> bool { + self.afi == other.afi && self.min <= other.max && other.min <= self.max + } +} + +impl IntervalValue for IpInterval { + fn start(&self) -> u128 { + self.min + } + + fn end(&self) -> u128 { + self.max + } + + fn overlaps(&self, other: &Self) -> bool { + IpInterval::overlaps(self, other) + } +} + +impl std::fmt::Display for IpInterval { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let min = u128_to_ip(self.afi, self.min); + let max = u128_to_ip(self.afi, self.max); + if min == max { + write!(formatter, "{min}") + } else { + write!(formatter, "{min} - {max}") + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct AsInterval { + min: u32, + max: u32, +} + +impl AsInterval { + fn new(min: u32, max: u32) -> Self { + Self { min, max } + } + + fn overlaps(&self, other: &Self) -> bool { + self.min <= other.max && other.min <= self.max + } +} + +impl IntervalValue for AsInterval { + fn start(&self) -> u128 { + self.min.into() + } + + fn end(&self) -> u128 { + self.max.into() + } + + fn overlaps(&self, other: &Self) -> bool { + AsInterval::overlaps(self, other) + } +} + +impl std::fmt::Display for AsInterval { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.min == self.max { + write!(formatter, "{}", self.min) + } else { + write!(formatter, "{} - {}", self.min, self.max) + } + } +} + +fn looks_like_ip_resource(resource: &str) -> bool { + resource.contains('.') || resource.contains(':') || resource.contains('/') +} + +fn parse_ip_interval(resource: &str) -> Result { + if let Some((raw_min, raw_max)) = resource.split_once('-') { + let min: IpAddr = raw_min.trim().parse().map_err(|_| "invalid range start")?; + let max: IpAddr = raw_max.trim().parse().map_err(|_| "invalid range end")?; + let (afi, min) = ip_to_u128(min); + let (max_afi, max) = ip_to_u128(max); + if afi != max_afi { + return Err("range endpoints use different address families".to_string()); + } + if min > max { + return Err("range start is greater than range end".to_string()); + } + return Ok(IpInterval::new(afi, min, max)); + } + + let (raw_address, raw_prefix_len) = resource + .split_once('/') + .ok_or_else(|| "expected CIDR prefix or range".to_string())?; + let address: IpAddr = raw_address.trim().parse().map_err(|_| "invalid address")?; + let prefix_len: u16 = raw_prefix_len + .trim() + .parse() + .map_err(|_| "invalid prefix length")?; + let (afi, address) = ip_to_u128(address); + let width = match afi { + Afi::Ipv4 => 32, + Afi::Ipv6 => 128, + }; + if prefix_len > width { + return Err(format!("prefix length must be <= {width}")); + } + let host_bits = width - prefix_len; + let mask = if prefix_len == 0 { + 0 + } else { + width_mask(width) << host_bits + }; + let min = address & mask; + let max = min | (!mask & width_mask(width)); + Ok(IpInterval::new(afi, min, max)) +} + +fn parse_as_interval(resource: &str) -> Result { + let parse_asn = |raw: &str| -> Result { + raw.trim() + .strip_prefix("AS") + .or_else(|| raw.trim().strip_prefix("as")) + .unwrap_or(raw.trim()) + .parse::() + .map_err(|_| "expected an ASN in the range 0..4294967295".to_string()) + }; + if let Some((raw_min, raw_max)) = resource.split_once('-') { + let min = parse_asn(raw_min)?; + let max = parse_asn(raw_max)?; + if min > max { + return Err("range start is greater than range end".to_string()); + } + Ok(AsInterval::new(min, max)) + } else { + let value = parse_asn(resource)?; + Ok(AsInterval::new(value, value)) + } +} + +fn normalize_ip_intervals(label: &str, entries: &mut Vec, warnings: &mut Vec) { + entries.sort_by_key(|entry| (entry.min, entry.max)); + let mut normalized = Vec::with_capacity(entries.len()); + for entry in entries.drain(..) { + let Some(last) = normalized.last_mut() else { + normalized.push(entry); + continue; + }; + if entry.min <= last.max { + warnings.push(format!( + "TA constraints {label} rules overlap; normalized without blocking startup" + )); + last.max = last.max.max(entry.max); + } else if entry.min == last.max.saturating_add(1) { + last.max = entry.max; + } else { + normalized.push(entry); + } + } + warnings.sort(); + warnings.dedup(); + *entries = normalized; +} + +fn normalize_as_intervals(label: &str, entries: &mut Vec, warnings: &mut Vec) { + entries.sort_by_key(|entry| (entry.min, entry.max)); + let mut normalized = Vec::with_capacity(entries.len()); + for entry in entries.drain(..) { + let Some(last) = normalized.last_mut() else { + normalized.push(entry); + continue; + }; + if entry.min <= last.max { + warnings.push(format!( + "TA constraints {label} rules overlap; normalized without blocking startup" + )); + last.max = last.max.max(entry.max); + } else if entry.min == last.max.saturating_add(1) { + last.max = entry.max; + } else { + normalized.push(entry); + } + } + warnings.sort(); + warnings.dedup(); + *entries = normalized; +} + +fn ip_item_to_interval( + afi: Afi, + item: &IpAddressOrRange, +) -> Result { + let (min, max) = match item { + IpAddressOrRange::Prefix(prefix) => { + let width = prefix.afi.ub(); + let address = ip_bytes_to_u128(&prefix.addr); + let prefix_len = prefix.prefix_len.min(width); + let host_bits = width - prefix_len; + let mask = if prefix_len == 0 { + 0 + } else { + width_mask(width) << host_bits + }; + let min = address & mask; + (min, min | (!mask & width_mask(width))) + } + IpAddressOrRange::Range(range) => { + (ip_bytes_to_u128(&range.min), ip_bytes_to_u128(&range.max)) + } + }; + if min > max { + return Err(TaConstraintsViolation( + "EE certificate carries an invalid IP range".to_string(), + )); + } + Ok(IpInterval::new(afi, min, max)) +} + +fn ip_to_u128(address: IpAddr) -> (Afi, u128) { + match address { + IpAddr::V4(address) => (Afi::Ipv4, u32::from(address) as u128), + IpAddr::V6(address) => (Afi::Ipv6, u128::from(address)), + } +} + +fn ip_bytes_to_u128(bytes: &[u8]) -> u128 { + bytes + .iter() + .fold(0u128, |value, byte| (value << 8) | u128::from(*byte)) +} + +fn width_mask(width: u16) -> u128 { + if width == 128 { + u128::MAX + } else { + (1u128 << width) - 1 + } +} + +fn u128_to_ip(afi: Afi, value: u128) -> IpAddr { + match afi { + Afi::Ipv4 => IpAddr::V4(Ipv4Addr::from(value as u32)), + Afi::Ipv6 => IpAddr::V6(Ipv6Addr::from(value)), + } +} + +fn afi_name(afi: Afi) -> &'static str { + match afi { + Afi::Ipv4 => "IPv4", + Afi::Ipv6 => "IPv6", + } +} diff --git a/src/ta_constraints/tests.rs b/src/ta_constraints/tests.rs new file mode 100644 index 0000000..78219b2 --- /dev/null +++ b/src/ta_constraints/tests.rs @@ -0,0 +1,313 @@ +// Resource constraint parser and interval-index tests. + +use super::{IntervalIndex, IntervalLookup, TaConstraints, TaConstraintsByTal}; + +fn parse(body: &str) -> TaConstraints { + TaConstraints::parse("test.constraints".into(), body).expect("parse constraints") +} + +fn indexed_ipv4_rules(action: &str, count: usize) -> String { + (0..count) + .map(|index| format!("{action} 10.{}.0.0/16\n", index * 2)) + .collect() +} + +fn indexed_ipv6_rules(action: &str, count: usize) -> String { + (0..count) + .map(|index| format!("{action} 2001:db8:{}::/48\n", index * 2)) + .collect() +} + +fn indexed_asn_rules(action: &str, count: usize) -> String { + (0..count) + .map(|index| format!("{action} {}\n", 65000 + index * 2)) + .collect() +} + +fn non_adjacent_ipv4_deny_rules(count: usize) -> String { + (0..count) + .map(|index| { + let value = index * 2; + format!("deny 10.{}.{}.0/24\n", value / 256, value % 256) + }) + .collect() +} + +#[test] +fn deny_has_precedence_and_unlisted_resources_are_denied() { + let constraints = parse("allow 192.0.2.0/24\ndeny 192.0.2.128/25\n"); + assert!( + constraints + .allow_v4 + .fully_covers(&super::parse_ip_interval("192.0.2.0/25").unwrap()) + ); + assert!( + constraints + .deny_v4 + .iter() + .any(|entry| entry.overlaps(&super::parse_ip_interval("192.0.2.128/25").unwrap())) + ); + assert!( + !constraints + .allow_v4 + .fully_covers(&super::parse_ip_interval("198.51.100.0/24").unwrap()) + ); +} + +#[test] +fn same_list_overlap_warns_and_normalizes() { + let constraints = parse("allow 10.0.0.0/8\nallow 10.1.0.0/16\n"); + assert_eq!(constraints.allow_v4.len(), 1); + assert_eq!(constraints.warnings.len(), 1); + assert!(constraints.warnings[0].contains("overlap")); +} + +#[test] +fn adjacent_allow_ranges_cover_one_interval() { + let constraints = parse("allow 64496 - 64500\nallow 64501 - 64511\n"); + assert!( + constraints + .allow_asn + .fully_covers(&super::parse_as_interval("64496 - 64511").unwrap()) + ); +} + +#[test] +fn interval_index_uses_linear_for_at_most_ten_and_tree_above_ten() { + for count in [0, 1, 9, 10] { + let constraints = parse(&indexed_ipv4_rules("deny", count)); + assert_eq!(constraints.deny_v4.len(), count); + assert!(!constraints.deny_v4.uses_tree(), "count={count}"); + } + + let constraints = parse(&indexed_ipv4_rules("deny", 11)); + assert_eq!(constraints.deny_v4.len(), 11); + assert!(constraints.deny_v4.uses_tree()); +} + +#[test] +fn tree_ip_queries_match_linear_reference() { + let body = format!( + "{}{}", + indexed_ipv4_rules("allow", 11), + indexed_ipv4_rules("deny", 11) + ); + let constraints = parse(&body); + assert!(constraints.allow_v4.uses_tree()); + assert!(constraints.deny_v4.uses_tree()); + + let linear_allow = IntervalIndex { + rules: constraints.allow_v4.rules.clone(), + lookup: IntervalLookup::Linear, + }; + let linear_deny = IntervalIndex { + rules: constraints.deny_v4.rules.clone(), + lookup: IntervalLookup::Linear, + }; + let targets = [ + super::parse_ip_interval("10.0.0.0/16").unwrap(), + super::parse_ip_interval("10.0.1.0/24").unwrap(), + super::parse_ip_interval("10.1.0.0/16").unwrap(), + super::parse_ip_interval("10.20.0.0/16").unwrap(), + super::parse_ip_interval("10.21.0.0/16").unwrap(), + super::parse_ip_interval("10.0.0.0 - 10.2.255.255").unwrap(), + super::parse_ip_interval("9.0.0.0/8").unwrap(), + super::parse_ip_interval("11.0.0.0/8").unwrap(), + ]; + for target in targets { + assert_eq!( + constraints.allow_v4.any_overlaps(&target), + linear_allow.any_overlaps(&target), + "allow overlap for {target}" + ); + assert_eq!( + constraints.allow_v4.fully_covers(&target), + linear_allow.fully_covers(&target), + "allow coverage for {target}" + ); + assert_eq!( + constraints.deny_v4.any_overlaps(&target), + linear_deny.any_overlaps(&target), + "deny overlap for {target}" + ); + } +} + +#[test] +fn tree_ipv6_queries_match_linear_reference() { + let body = format!( + "{}{}", + indexed_ipv6_rules("allow", 11), + indexed_ipv6_rules("deny", 11) + ); + let constraints = parse(&body); + assert!(constraints.allow_v6.uses_tree()); + assert!(constraints.deny_v6.uses_tree()); + + let linear_allow = IntervalIndex { + rules: constraints.allow_v6.rules.clone(), + lookup: IntervalLookup::Linear, + }; + let linear_deny = IntervalIndex { + rules: constraints.deny_v6.rules.clone(), + lookup: IntervalLookup::Linear, + }; + let targets = [ + super::parse_ip_interval("2001:db8:0::/48").unwrap(), + super::parse_ip_interval("2001:db8:1::/64").unwrap(), + super::parse_ip_interval("2001:db8:2::/48").unwrap(), + super::parse_ip_interval("2001:db8:20::/48").unwrap(), + super::parse_ip_interval("2001:db8:21::/48").unwrap(), + super::parse_ip_interval("2001:db8:0:: - 2001:db8:2:ffff:ffff:ffff:ffff:ffff").unwrap(), + super::parse_ip_interval("2001:db7::/32").unwrap(), + super::parse_ip_interval("2001:db9::/32").unwrap(), + ]; + for target in targets { + assert_eq!( + constraints.allow_v6.any_overlaps(&target), + linear_allow.any_overlaps(&target), + "allow overlap for {target}" + ); + assert_eq!( + constraints.allow_v6.fully_covers(&target), + linear_allow.fully_covers(&target), + "allow coverage for {target}" + ); + assert_eq!( + constraints.deny_v6.any_overlaps(&target), + linear_deny.any_overlaps(&target), + "deny overlap for {target}" + ); + } +} + +#[test] +fn tree_asn_queries_match_linear_reference() { + let body = format!( + "{}{}", + indexed_asn_rules("allow", 11), + indexed_asn_rules("deny", 11) + ); + let constraints = parse(&body); + assert!(constraints.allow_asn.uses_tree()); + assert!(constraints.deny_asn.uses_tree()); + + let linear_allow = IntervalIndex { + rules: constraints.allow_asn.rules.clone(), + lookup: IntervalLookup::Linear, + }; + let linear_deny = IntervalIndex { + rules: constraints.deny_asn.rules.clone(), + lookup: IntervalLookup::Linear, + }; + let targets = [ + super::AsInterval::new(65000, 65000), + super::AsInterval::new(65001, 65001), + super::AsInterval::new(65000, 65002), + super::AsInterval::new(65020, 65020), + super::AsInterval::new(65021, 65021), + super::AsInterval::new(64999, 65000), + ]; + for target in targets { + assert_eq!( + constraints.allow_asn.any_overlaps(&target), + linear_allow.any_overlaps(&target), + "allow overlap for {target}" + ); + assert_eq!( + constraints.allow_asn.fully_covers(&target), + linear_allow.fully_covers(&target), + "allow coverage for {target}" + ); + assert_eq!( + constraints.deny_asn.any_overlaps(&target), + linear_deny.any_overlaps(&target), + "deny overlap for {target}" + ); + } +} + +#[test] +fn index_equality_and_debug_ignore_derived_lookup() { + let constraints = parse(&indexed_ipv4_rules("deny", 11)); + let tree = constraints.deny_v4.clone(); + let linear = IntervalIndex { + rules: tree.rules.clone(), + lookup: IntervalLookup::Linear, + }; + assert_eq!(tree, linear); + assert_eq!(format!("{tree:?}"), format!("{linear:?}")); +} + +#[test] +fn adjacent_file_is_discovered_by_tal_stem() { + let dir = tempfile::tempdir().expect("tmpdir"); + let tal_path = dir.path().join("example.tal"); + std::fs::write(&tal_path, "placeholder").expect("write TAL"); + std::fs::write( + tal_path.with_extension("constraints"), + "allow 192.0.2.0/24\n", + ) + .expect("write constraints"); + let inputs = vec![crate::scheduler::types::TalInputSpec::from_file_path( + tal_path, + )]; + let loaded = TaConstraintsByTal::load_for_tals(&inputs, &[]).expect("load constraints"); + assert!(loaded.for_tal("example").is_some()); + let first = loaded + .shared_for_tal("example") + .expect("shared constraints snapshot"); + let second = loaded + .shared_for_tal("example") + .expect("shared constraints snapshot"); + assert!(std::sync::Arc::ptr_eq(&first, &second)); +} + +#[test] +fn shared_snapshots_remain_tal_specific() { + let dir = tempfile::tempdir().expect("tmpdir"); + let tal_a = dir.path().join("alpha.tal"); + let tal_b = dir.path().join("bravo.tal"); + std::fs::write(&tal_a, "placeholder").expect("write alpha TAL"); + std::fs::write(&tal_b, "placeholder").expect("write bravo TAL"); + std::fs::write(tal_a.with_extension("constraints"), "allow 192.0.2.0/24\n") + .expect("write alpha constraints"); + std::fs::write( + tal_b.with_extension("constraints"), + "allow 198.51.100.0/24\n", + ) + .expect("write bravo constraints"); + + let inputs = vec![ + crate::scheduler::types::TalInputSpec::from_file_path(tal_a), + crate::scheduler::types::TalInputSpec::from_file_path(tal_b), + ]; + let loaded = TaConstraintsByTal::load_for_tals(&inputs, &[]).expect("load constraints"); + let alpha = loaded.shared_for_tal("alpha").expect("alpha snapshot"); + let bravo = loaded.shared_for_tal("bravo").expect("bravo snapshot"); + assert!(!std::sync::Arc::ptr_eq(&alpha, &bravo)); + assert_ne!(alpha.as_ref(), bravo.as_ref()); +} + +#[test] +fn generated_large_ipv4_constraints_are_parseable_from_file() { + let rules = non_adjacent_ipv4_deny_rules(383); + let temp = tempfile::tempdir().expect("tempdir"); + let path = temp.path().join("large.constraints"); + std::fs::write(&path, format!("allow 0.0.0.0/0\n{rules}")).expect("write constraints"); + let constraints = TaConstraints::from_file(&path).expect("parse generated constraints"); + assert_eq!(constraints.allow_v4.len(), 1); + assert_eq!(constraints.deny_v4.len(), 383); + assert!(constraints.deny_v4.uses_tree()); +} + +#[test] +fn generated_very_large_ipv4_constraints_use_interval_tree() { + let rules = non_adjacent_ipv4_deny_rules(1825); + let constraints = parse(&format!("allow 0.0.0.0/0\n{rules}")); + assert_eq!(constraints.allow_v4.len(), 1); + assert_eq!(constraints.deny_v4.len(), 1825); + assert!(constraints.deny_v4.uses_tree()); + assert!(!constraints.allow_v4.uses_tree()); + assert!(constraints.warnings.is_empty()); +} diff --git a/src/test_support.rs b/src/test_support.rs new file mode 100644 index 0000000..91fe83d --- /dev/null +++ b/src/test_support.rs @@ -0,0 +1,48 @@ +//! Test-only, self-generated RPKI publication point. +//! +//! This intentionally keeps published RIR repository objects out of the Panda RPKI +//! source tree. The generator makes fresh keys and RFC-shaped objects for each +//! test process, so the tests neither contact nor redistribute production RPKI +//! data. + +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::sync::OnceLock; + +pub(crate) struct SyntheticRepositoryFixture { + _directory: tempfile::TempDir, + root: PathBuf, +} + +impl SyntheticRepositoryFixture { + pub(crate) fn case_repository(&self, case: &str) -> PathBuf { + self.root.join("cases").join(case).join("repository") + } + + pub(crate) fn tal(&self) -> PathBuf { + self.root.join("tal/custom.tal") + } + + pub(crate) fn trust_anchor(&self) -> PathBuf { + self.root.join("ta/custom-ta.cer") + } +} + +pub(crate) fn synthetic_repository() -> &'static SyntheticRepositoryFixture { + static FIXTURE: OnceLock = OnceLock::new(); + FIXTURE.get_or_init(|| { + let directory = tempfile::tempdir().expect("create synthetic fixture directory"); + let root = directory.path().join("repository"); + let status = Command::new("python3") + .arg(Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/support/generate_repository.py")) + .arg("--output") + .arg(&root) + .status() + .expect("start synthetic RPKI fixture generator"); + assert!(status.success(), "synthetic RPKI fixture generator failed"); + SyntheticRepositoryFixture { + _directory: directory, + root, + } + }) +} diff --git a/src/validation/ca_instance.rs b/src/validation/ca_instance.rs new file mode 100644 index 0000000..ed654f5 --- /dev/null +++ b/src/validation/ca_instance.rs @@ -0,0 +1,132 @@ +use crate::model::oid::{OID_AD_CA_REPOSITORY, OID_AD_RPKI_MANIFEST, OID_AD_RPKI_NOTIFY}; +use crate::model::rc::{ResourceCertKind, ResourceCertificate, SubjectInfoAccess}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CaInstanceUris { + /// rsync sync base URI used for live/materialized publication point fetches (must end with `/`). + /// + /// This is the parent directory of `manifest_rsync_uri`, not necessarily the full + /// `id-ad-caRepository` URI. Using the manifest parent keeps sync scope narrow while + /// `publication_point_rsync_uri` preserves the full RFC 9286 publication-point value. + pub rsync_base_uri: String, + /// rsync URI for the manifest object (`.mft`). + pub manifest_rsync_uri: String, + /// Publication point rsync URI (RFC 9286 terminology). + pub publication_point_rsync_uri: String, + /// Optional RRDP notification URI (https). + pub rrdp_notification_uri: Option, +} + +#[derive(Debug, thiserror::Error)] +pub enum CaInstanceUrisError { + #[error("certificate must be a CA certificate (RFC 6487 §4.8.1)")] + NotCa, + + #[error( + "CA certificate must contain Subject Information Access extension (RFC 6487 §4.8.8; RFC 5280 §4.2.2.2)" + )] + MissingSia, + + #[error("CA certificate SIA must contain id-ad-caRepository rsync URI (RFC 6487 §4.8.8.1)")] + MissingCaRepository, + + #[error("CA certificate SIA must contain id-ad-rpkiManifest rsync URI (RFC 6487 §4.8.8.2)")] + MissingRpkiManifest, + + #[error( + "SIA id-ad-caRepository accessLocation must be rsync:// URI, got {0} (RFC 6487 §4.8.8.1)" + )] + CaRepositoryNotRsync(String), + + #[error( + "SIA id-ad-rpkiManifest accessLocation must be rsync:// URI, got {0} (RFC 6487 §4.8.8.2)" + )] + RpkiManifestNotRsync(String), + + #[error( + "SIA id-ad-rpkiNotify accessLocation must be https:// URI, got {0} (RFC 8182 §3.4.1; RFC 6487 §4.8.8.3)" + )] + RpkiNotifyNotHttps(String), + + #[error( + "manifest rsync URI must be under CA publication point: manifest={manifest_rsync_uri} publication_point={publication_point_rsync_uri} (RFC 9286 §6.1)" + )] + ManifestNotUnderPublicationPoint { + manifest_rsync_uri: String, + publication_point_rsync_uri: String, + }, +} + +pub fn ca_instance_uris_from_ca_certificate( + cert: &ResourceCertificate, +) -> Result { + if cert.kind != ResourceCertKind::Ca { + return Err(CaInstanceUrisError::NotCa); + } + + let sia = cert + .tbs + .extensions + .subject_info_access + .as_ref() + .ok_or(CaInstanceUrisError::MissingSia)?; + + let access_descriptions = match sia { + SubjectInfoAccess::Ca(ca) => &ca.access_descriptions, + SubjectInfoAccess::Ee(_ee) => return Err(CaInstanceUrisError::MissingSia), + }; + + let mut ca_repo: Option = None; + let mut manifest: Option = None; + let mut notify: Option = None; + + for ad in access_descriptions { + if ad.access_method_oid == OID_AD_CA_REPOSITORY { + let u = ad.access_location.as_str(); + if !u.starts_with("rsync://") { + return Err(CaInstanceUrisError::CaRepositoryNotRsync(u.to_string())); + } + ca_repo.get_or_insert(u.to_string()); + } else if ad.access_method_oid == OID_AD_RPKI_MANIFEST { + let u = ad.access_location.as_str(); + if !u.starts_with("rsync://") { + return Err(CaInstanceUrisError::RpkiManifestNotRsync(u.to_string())); + } + manifest.get_or_insert(u.to_string()); + } else if ad.access_method_oid == OID_AD_RPKI_NOTIFY { + let u = ad.access_location.as_str(); + if !u.starts_with("https://") { + return Err(CaInstanceUrisError::RpkiNotifyNotHttps(u.to_string())); + } + notify.get_or_insert(u.to_string()); + } + } + + let mut publication_point_rsync_uri = + ca_repo.ok_or(CaInstanceUrisError::MissingCaRepository)?; + if !publication_point_rsync_uri.ends_with('/') { + publication_point_rsync_uri.push('/'); + } + + let manifest_rsync_uri = manifest.ok_or(CaInstanceUrisError::MissingRpkiManifest)?; + if !manifest_rsync_uri.starts_with(&publication_point_rsync_uri) { + return Err(CaInstanceUrisError::ManifestNotUnderPublicationPoint { + manifest_rsync_uri, + publication_point_rsync_uri, + }); + } + let manifest_parent = manifest_rsync_uri + .rsplit_once('/') + .map(|(parent, _)| format!("{parent}/")) + .ok_or_else(|| CaInstanceUrisError::ManifestNotUnderPublicationPoint { + manifest_rsync_uri: manifest_rsync_uri.clone(), + publication_point_rsync_uri: publication_point_rsync_uri.clone(), + })?; + + Ok(CaInstanceUris { + rsync_base_uri: manifest_parent, + manifest_rsync_uri, + publication_point_rsync_uri, + rrdp_notification_uri: notify, + }) +} diff --git a/src/validation/ca_path.rs b/src/validation/ca_path.rs new file mode 100644 index 0000000..b39452d --- /dev/null +++ b/src/validation/ca_path.rs @@ -0,0 +1,11 @@ +#![allow(clippy::too_many_arguments, clippy::type_complexity)] + +include!("ca_path/types_and_validation.rs"); +include!("ca_path/certificate_checks.rs"); +include!("ca_path/resource_resolution.rs"); +include!("ca_path/ip_resources.rs"); +include!("ca_path/increment.rs"); + +#[cfg(test)] +#[path = "ca_path/tests.rs"] +mod tests; diff --git a/src/validation/ca_path/certificate_checks.rs b/src/validation/ca_path/certificate_checks.rs new file mode 100644 index 0000000..8eebb32 --- /dev/null +++ b/src/validation/ca_path/certificate_checks.rs @@ -0,0 +1,138 @@ +// Certificate profile, signature, CRL, and validity checks. + +fn parse_subject_pki_from_der(der: &[u8]) -> Result, CaPathError> { + let (rem, spki) = SubjectPublicKeyInfo::from_der(der) + .map_err(|e| CaPathError::IssuerSpkiParse(e.to_string()))?; + if !rem.is_empty() { + return Err(CaPathError::IssuerSpkiTrailingBytes(rem.len())); + } + Ok(spki) +} + +fn parse_x509_cert(der: &[u8]) -> Result, CaPathError> { + let (rem, cert) = X509Certificate::from_der(der) + .map_err(|e| CaPathError::ChildSignatureInvalid(e.to_string()))?; + if !rem.is_empty() { + return Err(CaPathError::ChildSignatureInvalid( + "trailing bytes after child certificate".to_string(), + )); + } + Ok(cert) +} + +fn verify_child_signature( + child: &X509Certificate<'_>, + issuer_spki: &SubjectPublicKeyInfo<'_>, +) -> Result<(), CaPathError> { + + child + .verify_signature(Some(issuer_spki)) + .map_err(|e| CaPathError::ChildSignatureInvalid(e.to_string())) +} + +fn validate_child_aki_matches_issuer_ski( + child: &ResourceCertificate, + issuer: &ResourceCertificate, +) -> Result<(), CaPathError> { + let Some(issuer_ski) = issuer.tbs.extensions.subject_key_identifier.as_deref() else { + return Err(CaPathError::IssuerSkiMissing); + }; + let Some(child_aki) = child.tbs.extensions.authority_key_identifier.as_deref() else { + return Err(CaPathError::ChildAkiMissing); + }; + if child_aki != issuer_ski { + return Err(CaPathError::ChildAkiMismatch); + } + Ok(()) +} + +fn validate_child_aia_points_to_issuer_uri( + child: &ResourceCertificate, + issuer_ca_rsync_uri: &str, +) -> Result<(), CaPathError> { + let Some(uris) = child.tbs.extensions.ca_issuers_uris.as_ref() else { + return Err(CaPathError::ChildAiaMissing); + }; + if !uris.iter().any(|u| u.as_str() == issuer_ca_rsync_uri) { + return Err(CaPathError::ChildAiaIssuerUriMismatch); + } + Ok(()) +} + +fn validate_child_crldp_contains_issuer_crl_uri( + child: &ResourceCertificate, + issuer_crl_rsync_uri: &str, +) -> Result<(), CaPathError> { + let Some(uris) = child.tbs.extensions.crl_distribution_points_uris.as_ref() else { + return Err(CaPathError::ChildCrlDpMissing); + }; + if !uris.iter().any(|u| u.as_str() == issuer_crl_rsync_uri) { + return Err(CaPathError::ChildCrlDpUriMismatch); + } + Ok(()) +} + +fn validate_child_ca_key_usage(cert: &X509Certificate<'_>) -> Result<(), CaPathError> { + let mut ku_critical: Option = None; + for ext in cert.extensions() { + if ext.oid.as_bytes() == OID_KEY_USAGE_RAW { + ku_critical = Some(ext.critical); + break; + } + } + + let Some(critical) = ku_critical else { + return Err(CaPathError::KeyUsageMissing); + }; + if !critical { + return Err(CaPathError::KeyUsageNotCritical); + } + + let Some(ku) = cert + .key_usage() + .map_err(|e| CaPathError::ChildSignatureInvalid(e.to_string()))? + else { + return Err(CaPathError::KeyUsageMissing); + }; + + let v = &ku.value; + let ok = v.key_cert_sign() + && v.crl_sign() + && !v.digital_signature() + && !v.non_repudiation() + && !v.key_encipherment() + && !v.data_encipherment() + && !v.key_agreement() + && !v.encipher_only() + && !v.decipher_only(); + if !ok { + return Err(CaPathError::KeyUsageInvalidBits); + } + + Ok(()) +} + +fn time_within_validity( + t: time::OffsetDateTime, + not_before: time::OffsetDateTime, + not_after: time::OffsetDateTime, +) -> bool { + let t = t.to_offset(time::UtcOffset::UTC); + let not_before = not_before.to_offset(time::UtcOffset::UTC); + let not_after = not_after.to_offset(time::UtcOffset::UTC); + t >= not_before && t <= not_after +} + +fn crl_valid_at_time(crl: &RpkixCrl, t: time::OffsetDateTime) -> bool { + let t = t.to_offset(time::UtcOffset::UTC); + let this_update = crl.this_update.utc.to_offset(time::UtcOffset::UTC); + let next_update = crl.next_update.utc.to_offset(time::UtcOffset::UTC); + t >= this_update && t < next_update +} + +fn is_serial_revoked_by_crl(cert: &ResourceCertificate, crl: &RpkixCrl) -> bool { + let serial = BigUnsigned::from_biguint(&cert.tbs.serial_number); + crl.revoked_certs + .iter() + .any(|rc| rc.serial_number == serial) +} diff --git a/src/validation/ca_path/increment.rs b/src/validation/ca_path/increment.rs new file mode 100644 index 0000000..7eba763 --- /dev/null +++ b/src/validation/ca_path/increment.rs @@ -0,0 +1,13 @@ +// Small byte-vector helper used by resource interval tests. + +fn increment_bytes(v: &[u8]) -> Vec { + let mut out = v.to_vec(); + for i in (0..out.len()).rev() { + if out[i] != 0xFF { + out[i] += 1; + out[i + 1..].fill(0); + return out; + } + } + vec![0u8; out.len()] +} diff --git a/src/validation/ca_path/ip_resources.rs b/src/validation/ca_path/ip_resources.rs new file mode 100644 index 0000000..f8c449e --- /dev/null +++ b/src/validation/ca_path/ip_resources.rs @@ -0,0 +1,274 @@ +// IP interval normalization, intersection, and coverage helpers. + +fn ip_resources_by_afi_items( + set: &IpResourceSet, +) -> Result< + std::collections::BTreeMap< + crate::model::rc::Afi, + Vec, + >, + CaPathError, +> { + let mut m: std::collections::BTreeMap< + crate::model::rc::Afi, + Vec, + > = std::collections::BTreeMap::new(); + for fam in &set.families { + match &fam.choice { + IpAddressChoice::Inherit => return Err(CaPathError::InheritWithoutParentResources), + IpAddressChoice::AddressesOrRanges(items) => { + m.insert(fam.afi, items.clone()); + } + } + } + Ok(m) +} + +fn ip_resources_to_merged_intervals_by_afi( + set: &IpResourceSet, +) -> HashMap, Vec)>> { + let mut m: HashMap, Vec)>> = HashMap::new(); + + for fam in &set.families { + match &fam.choice { + IpAddressChoice::Inherit => { + // When used in subset checks, treat inherit as "all" by leaving it absent. + // Resolution should have happened earlier. + } + IpAddressChoice::AddressesOrRanges(items) => { + let ent = m.entry(fam.afi).or_default(); + for item in items { + match item { + crate::model::rc::IpAddressOrRange::Prefix(p) => { + let (min, max) = prefix_to_range(p); + ent.push((min, max)); + } + crate::model::rc::IpAddressOrRange::Range(r) => { + ent.push((r.min.clone(), r.max.clone())); + } + } + } + } + } + } + + for (_afi, v) in m.iter_mut() { + v.sort_by(|(a, _), (b, _)| a.cmp(b)); + *v = merge_ip_intervals(v); + } + + m +} + +fn ip_family_items_subset_with_parent_intervals( + child_items: &[crate::model::rc::IpAddressOrRange], + parent_intervals: &[(Vec, Vec)], +) -> bool { + if parent_intervals.is_empty() { + return false; + } + + let mut child_intervals: Vec<(Vec, Vec)> = Vec::new(); + for item in child_items { + match item { + crate::model::rc::IpAddressOrRange::Prefix(p) => { + child_intervals.push(prefix_to_range(p)) + } + crate::model::rc::IpAddressOrRange::Range(r) => { + child_intervals.push((r.min.clone(), r.max.clone())) + } + } + } + child_intervals.sort_by(|(a, _), (b, _)| a.cmp(b)); + let child_intervals = merge_ip_intervals(&child_intervals); + + for (cmin, cmax) in &child_intervals { + if !interval_is_covered(parent_intervals, cmin, cmax) { + return false; + } + } + true +} + +fn ip_items_to_merged_intervals( + items: &[crate::model::rc::IpAddressOrRange], +) -> Vec<(Vec, Vec)> { + let mut intervals = Vec::new(); + for item in items { + match item { + crate::model::rc::IpAddressOrRange::Prefix(p) => { + intervals.push(prefix_to_range(p)) + } + crate::model::rc::IpAddressOrRange::Range(r) => { + intervals.push((r.min.clone(), r.max.clone())) + } + } + } + intervals.sort_by(|(a, _), (b, _)| a.cmp(b)); + merge_ip_intervals(&intervals) +} + +fn intersect_ip_items_with_parent_intervals( + items: &[crate::model::rc::IpAddressOrRange], + parent_intervals: &[(Vec, Vec)], +) -> Vec<(Vec, Vec)> { + let child_intervals = ip_items_to_merged_intervals(items); + intersect_ip_intervals(&child_intervals, parent_intervals) +} + +fn intersect_ip_intervals( + child_intervals: &[(Vec, Vec)], + parent_intervals: &[(Vec, Vec)], +) -> Vec<(Vec, Vec)> { + let mut out = Vec::new(); + let mut parent_index = 0usize; + for (child_min, child_max) in child_intervals { + while parent_index < parent_intervals.len() + && parent_intervals[parent_index].1.as_slice() < child_min.as_slice() + { + parent_index += 1; + } + let mut scan = parent_index; + while scan < parent_intervals.len() + && parent_intervals[scan].0.as_slice() <= child_max.as_slice() + { + let (parent_min, parent_max) = &parent_intervals[scan]; + let min = if bytes_leq(child_min, parent_min) { + parent_min.clone() + } else { + child_min.clone() + }; + let max = if bytes_leq(child_max, parent_max) { + child_max.clone() + } else { + parent_max.clone() + }; + if bytes_leq(&min, &max) { + out.push((min, max)); + } + scan += 1; + } + } + merge_ip_intervals(&out) +} + +fn ip_intervals_to_ranges( + afi: crate::model::rc::Afi, + intervals: &[(Vec, Vec)], +) -> Vec { + intervals + .iter() + .map(|(min, max)| { + crate::model::rc::IpAddressOrRange::Range(crate::model::rc::IpAddressRange { + min: normalize_ip_bytes(afi, min), + max: normalize_ip_bytes(afi, max), + }) + }) + .collect() +} + +fn normalize_ip_bytes(afi: crate::model::rc::Afi, bytes: &[u8]) -> Vec { + let target_len = afi.octets_len(); + if bytes.len() == target_len { + return bytes.to_vec(); + } + let mut out = vec![0u8; target_len]; + let copy_len = bytes.len().min(target_len); + out[..copy_len].copy_from_slice(&bytes[..copy_len]); + out +} + +fn intervals_changed(left: &[T], right: &[T]) -> bool { + left != right +} + +fn intersect_as_intervals(child: &[(u32, u32)], parent: &[(u32, u32)]) -> Vec<(u32, u32)> { + let mut out = Vec::new(); + let mut parent_index = 0usize; + for (child_min, child_max) in child { + while parent_index < parent.len() && parent[parent_index].1 < *child_min { + parent_index += 1; + } + let mut scan = parent_index; + while scan < parent.len() && parent[scan].0 <= *child_max { + let min = (*child_min).max(parent[scan].0); + let max = (*child_max).min(parent[scan].1); + if min <= max { + out.push((min, max)); + } + scan += 1; + } + } + merge_as_intervals(&out) +} + +fn as_intervals_to_items(intervals: &[(u32, u32)]) -> Vec { + intervals + .iter() + .map(|(min, max)| { + if min == max { + crate::model::rc::AsIdOrRange::Id(*min) + } else { + crate::model::rc::AsIdOrRange::Range { + min: *min, + max: *max, + } + } + }) + .collect() +} + +fn merge_ip_intervals(v: &[(Vec, Vec)]) -> Vec<(Vec, Vec)> { + let mut out: Vec<(Vec, Vec)> = Vec::new(); + for (min, max) in v { + let Some(last) = out.last_mut() else { + out.push((min.clone(), max.clone())); + continue; + }; + + if bytes_leq(min, &increment_bytes(&last.1)) { + if bytes_leq(&last.1, max) { + last.1 = max.clone(); + } + continue; + } + + out.push((min.clone(), max.clone())); + } + out +} + +fn interval_is_covered(parent: &[(Vec, Vec)], min: &[u8], max: &[u8]) -> bool { + for (pmin, pmax) in parent { + if bytes_leq(pmin, min) && bytes_leq(max, pmax) { + return true; + } + if pmin.as_slice() > min { + break; + } + } + false +} + +fn prefix_to_range(prefix: &crate::model::rc::IpPrefix) -> (Vec, Vec) { + let mut min = prefix.addr.clone(); + let mut max = prefix.addr.clone(); + + let bitlen = match prefix.afi { + crate::model::rc::Afi::Ipv4 => 32u16, + crate::model::rc::Afi::Ipv6 => 128u16, + }; + let plen = prefix.prefix_len.min(bitlen); + for bit in plen..bitlen { + let byte = (bit / 8) as usize; + let offset = 7 - (bit % 8); + let mask = 1u8 << offset; + min[byte] &= !mask; + max[byte] |= mask; + } + (min, max) +} + +fn bytes_leq(a: &[u8], b: &[u8]) -> bool { + a <= b +} diff --git a/src/validation/ca_path/resource_resolution.rs b/src/validation/ca_path/resource_resolution.rs new file mode 100644 index 0000000..5f12921 --- /dev/null +++ b/src/validation/ca_path/resource_resolution.rs @@ -0,0 +1,440 @@ +// Effective IP/AS resource resolution and interval helpers. + +#[derive(Clone, Debug)] +struct ResourceResolution { + effective_ip_resources: Option, + effective_as_resources: Option, + warnings: ResourceValidationWarnings, +} + +fn resolve_child_resources( + child_ip: Option<&IpResourceSet>, + issuer_effective_ip: Option<&IpResourceSet>, + child_as: Option<&AsResourceSet>, + issuer_effective_as: Option<&AsResourceSet>, + issuer_resources_index: &IssuerEffectiveResourcesIndex, + mode: ResourceValidationMode, +) -> Result { + match mode { + ResourceValidationMode::Rfc6487 => Ok(ResourceResolution { + effective_ip_resources: resolve_child_ip_resources_indexed( + child_ip, + issuer_effective_ip, + issuer_resources_index.parent_ip_by_afi_items.as_ref(), + &issuer_resources_index.parent_ip_merged_intervals, + )?, + effective_as_resources: resolve_child_as_resources_indexed( + child_as, + issuer_effective_as, + issuer_resources_index.parent_asnum_intervals.as_deref(), + issuer_resources_index.parent_rdi_intervals.as_deref(), + )?, + warnings: ResourceValidationWarnings::default(), + }), + ResourceValidationMode::ValidationUpdate03 => { + let (effective_ip_resources, ip_reduced_by_vrs, ip_vrs_empty) = + resolve_child_ip_resources_vrs( + child_ip, + issuer_effective_ip, + issuer_resources_index.parent_ip_by_afi_items.as_ref(), + &issuer_resources_index.parent_ip_merged_intervals, + )?; + let (effective_as_resources, as_reduced_by_vrs, as_vrs_empty) = + resolve_child_as_resources_vrs( + child_as, + issuer_effective_as, + issuer_resources_index.parent_asnum_intervals.as_deref(), + issuer_resources_index.parent_rdi_intervals.as_deref(), + )?; + Ok(ResourceResolution { + effective_ip_resources, + effective_as_resources, + warnings: ResourceValidationWarnings { + ip_reduced_by_vrs, + as_reduced_by_vrs, + ip_vrs_empty, + as_vrs_empty, + }, + }) + } + } +} + +#[cfg(test)] +fn resolve_child_ip_resources( + child_ip: Option<&IpResourceSet>, + issuer_effective: Option<&IpResourceSet>, +) -> Result, CaPathError> { + let precomputed_parent_by_afi = issuer_effective + .map(ip_resources_by_afi_items) + .transpose()?; + let precomputed_parent_intervals = issuer_effective + .map(ip_resources_to_merged_intervals_by_afi) + .unwrap_or_default(); + resolve_child_ip_resources_indexed( + child_ip, + issuer_effective, + precomputed_parent_by_afi.as_ref(), + &precomputed_parent_intervals, + ) +} + +fn resolve_child_ip_resources_indexed( + child_ip: Option<&IpResourceSet>, + issuer_effective: Option<&IpResourceSet>, + parent_by_afi: Option< + &BTreeMap>, + >, + parent_intervals_by_afi: &HashMap, Vec)>>, +) -> Result, CaPathError> { + let Some(child_ip) = child_ip else { + return Ok(None); + }; + + let Some(_parent) = issuer_effective else { + if child_ip.has_any_inherit() { + return Err(CaPathError::InheritWithoutParentResources); + } + // With no parent effective resources, we cannot validate subset. + return Err(CaPathError::ResourcesNotSubset); + }; + + // Resolve per-AFI inherit, producing an effective set with no inherit. + let parent_by_afi = parent_by_afi.ok_or(CaPathError::InheritWithoutParentResources)?; + let mut out_families: Vec = Vec::new(); + + for fam in &child_ip.families { + match &fam.choice { + IpAddressChoice::Inherit => { + let items = parent_by_afi + .get(&fam.afi) + .ok_or(CaPathError::InheritWithoutParentResources)?; + out_families.push(crate::model::rc::IpAddressFamily { + afi: fam.afi, + choice: IpAddressChoice::AddressesOrRanges(items.clone()), + }); + } + IpAddressChoice::AddressesOrRanges(items) => { + // Subset check against parent union for that AFI. + let parent_intervals = parent_intervals_by_afi + .get(&fam.afi) + .map(Vec::as_slice) + .unwrap_or(&[]); + if !ip_family_items_subset_with_parent_intervals(items, parent_intervals) { + return Err(CaPathError::ResourcesNotSubset); + } + out_families.push(crate::model::rc::IpAddressFamily { + afi: fam.afi, + choice: IpAddressChoice::AddressesOrRanges(items.clone()), + }); + } + } + } + + Ok(Some(IpResourceSet { + families: out_families, + })) +} + +fn resolve_child_ip_resources_vrs( + child_ip: Option<&IpResourceSet>, + issuer_effective: Option<&IpResourceSet>, + parent_by_afi: Option< + &BTreeMap>, + >, + parent_intervals_by_afi: &HashMap, Vec)>>, +) -> Result<(Option, bool, bool), CaPathError> { + let Some(child_ip) = child_ip else { + return Ok((None, false, false)); + }; + if child_ip.has_any_inherit() && issuer_effective.is_none() { + return Err(CaPathError::InheritWithoutParentResources); + } + + let parent_by_afi = parent_by_afi.unwrap_or_else(|| { + static EMPTY: std::sync::OnceLock< + BTreeMap>, + > = std::sync::OnceLock::new(); + EMPTY.get_or_init(BTreeMap::new) + }); + let mut out_families = Vec::new(); + let mut reduced = false; + let mut saw_declared = false; + + for fam in &child_ip.families { + match &fam.choice { + IpAddressChoice::Inherit => { + let items = parent_by_afi + .get(&fam.afi) + .ok_or(CaPathError::InheritWithoutParentResources)?; + out_families.push(crate::model::rc::IpAddressFamily { + afi: fam.afi, + choice: IpAddressChoice::AddressesOrRanges(items.clone()), + }); + } + IpAddressChoice::AddressesOrRanges(items) => { + saw_declared = saw_declared || !items.is_empty(); + let parent_intervals = parent_intervals_by_afi + .get(&fam.afi) + .map(Vec::as_slice) + .unwrap_or(&[]); + let intersections = + intersect_ip_items_with_parent_intervals(items, parent_intervals); + let child_intervals = ip_items_to_merged_intervals(items); + if intervals_changed(&child_intervals, &intersections) { + reduced = true; + } + if !intersections.is_empty() { + out_families.push(crate::model::rc::IpAddressFamily { + afi: fam.afi, + choice: IpAddressChoice::AddressesOrRanges(ip_intervals_to_ranges( + fam.afi, + &intersections, + )), + }); + } + } + } + } + + let empty = saw_declared && out_families.is_empty(); + Ok(( + Some(IpResourceSet { + families: out_families, + }), + reduced, + empty, + )) +} + +#[cfg(test)] +fn resolve_child_as_resources( + child_as: Option<&AsResourceSet>, + issuer_effective: Option<&AsResourceSet>, +) -> Result, CaPathError> { + let precomputed_asnum = issuer_effective + .and_then(|resources| resources.asnum.as_ref().map(as_choice_to_merged_intervals)); + let precomputed_rdi = issuer_effective + .and_then(|resources| resources.rdi.as_ref().map(as_choice_to_merged_intervals)); + resolve_child_as_resources_indexed( + child_as, + issuer_effective, + precomputed_asnum.as_deref(), + precomputed_rdi.as_deref(), + ) +} + +fn resolve_child_as_resources_indexed( + child_as: Option<&AsResourceSet>, + issuer_effective: Option<&AsResourceSet>, + parent_asnum_intervals: Option<&[(u32, u32)]>, + parent_rdi_intervals: Option<&[(u32, u32)]>, +) -> Result, CaPathError> { + let Some(child_as) = child_as else { + return Ok(None); + }; + let Some(parent) = issuer_effective else { + if matches!(child_as.asnum, Some(AsIdentifierChoice::Inherit)) + || matches!(child_as.rdi, Some(AsIdentifierChoice::Inherit)) + { + return Err(CaPathError::InheritWithoutParentResources); + } + return Err(CaPathError::ResourcesNotSubset); + }; + + let asnum = match child_as.asnum.as_ref() { + None => None, + Some(AsIdentifierChoice::Inherit) => parent + .asnum + .clone() + .ok_or(CaPathError::InheritWithoutParentResources) + .map(Some)?, + Some(_) => { + if !as_choice_subset_with_parent_intervals( + child_as.asnum.as_ref(), + parent.asnum.as_ref(), + parent_asnum_intervals, + ) { + return Err(CaPathError::ResourcesNotSubset); + } + child_as.asnum.clone() + } + }; + + let rdi = match child_as.rdi.as_ref() { + None => None, + Some(AsIdentifierChoice::Inherit) => parent + .rdi + .clone() + .ok_or(CaPathError::InheritWithoutParentResources) + .map(Some)?, + Some(_) => { + if !as_choice_subset_with_parent_intervals( + child_as.rdi.as_ref(), + parent.rdi.as_ref(), + parent_rdi_intervals, + ) { + return Err(CaPathError::ResourcesNotSubset); + } + child_as.rdi.clone() + } + }; + + Ok(Some(AsResourceSet { asnum, rdi })) +} + +fn resolve_child_as_resources_vrs( + child_as: Option<&AsResourceSet>, + issuer_effective: Option<&AsResourceSet>, + parent_asnum_intervals: Option<&[(u32, u32)]>, + parent_rdi_intervals: Option<&[(u32, u32)]>, +) -> Result<(Option, bool, bool), CaPathError> { + let Some(child_as) = child_as else { + return Ok((None, false, false)); + }; + if issuer_effective.is_none() + && (matches!(child_as.asnum, Some(AsIdentifierChoice::Inherit)) + || matches!(child_as.rdi, Some(AsIdentifierChoice::Inherit))) + { + return Err(CaPathError::InheritWithoutParentResources); + } + + let (asnum, asnum_reduced, asnum_empty) = resolve_as_choice_vrs( + child_as.asnum.as_ref(), + issuer_effective.and_then(|p| p.asnum.as_ref()), + parent_asnum_intervals, + )?; + let (rdi, rdi_reduced, rdi_empty) = resolve_as_choice_vrs( + child_as.rdi.as_ref(), + issuer_effective.and_then(|p| p.rdi.as_ref()), + parent_rdi_intervals, + )?; + Ok(( + Some(AsResourceSet { asnum, rdi }), + asnum_reduced || rdi_reduced, + asnum_empty || rdi_empty, + )) +} + +fn resolve_as_choice_vrs( + child: Option<&AsIdentifierChoice>, + parent: Option<&AsIdentifierChoice>, + parent_intervals_hint: Option<&[(u32, u32)]>, +) -> Result<(Option, bool, bool), CaPathError> { + let Some(child) = child else { + return Ok((None, false, false)); + }; + match child { + AsIdentifierChoice::Inherit => { + let parent = parent + .cloned() + .ok_or(CaPathError::InheritWithoutParentResources)?; + Ok((Some(parent), false, false)) + } + AsIdentifierChoice::AsIdsOrRanges(_) => { + let child_intervals = as_choice_to_merged_intervals(child); + let parent_intervals; + let parent_intervals = match parent_intervals_hint { + Some(v) => v, + None => { + parent_intervals = parent + .map(as_choice_to_merged_intervals) + .unwrap_or_default(); + parent_intervals.as_slice() + } + }; + let intersections = intersect_as_intervals(&child_intervals, parent_intervals); + let reduced = child_intervals != intersections; + let empty = !child_intervals.is_empty() && intersections.is_empty(); + Ok(( + Some(AsIdentifierChoice::AsIdsOrRanges(as_intervals_to_items( + &intersections, + ))), + reduced, + empty, + )) + } + } +} + +fn as_choice_subset_with_parent_intervals( + child: Option<&AsIdentifierChoice>, + parent: Option<&AsIdentifierChoice>, + parent_intervals_hint: Option<&[(u32, u32)]>, +) -> bool { + let Some(child) = child else { + return true; + }; + let Some(parent) = parent else { + return false; + }; + + // Treat inherit as "all of parent" here; actual resolution is handled elsewhere. + if matches!(child, AsIdentifierChoice::Inherit) { + return true; + } + if matches!(parent, AsIdentifierChoice::Inherit) { + return true; + } + + let child_intervals = as_choice_to_merged_intervals(child); + let owned_parent_intervals; + let parent_intervals = match parent_intervals_hint { + Some(intervals) => intervals, + None => { + owned_parent_intervals = as_choice_to_merged_intervals(parent); + owned_parent_intervals.as_slice() + } + }; + for (cmin, cmax) in &child_intervals { + if !as_interval_is_covered(parent_intervals, *cmin, *cmax) { + return false; + } + } + true +} + +fn as_choice_to_merged_intervals(choice: &AsIdentifierChoice) -> Vec<(u32, u32)> { + let mut v = Vec::new(); + match choice { + AsIdentifierChoice::Inherit => {} + AsIdentifierChoice::AsIdsOrRanges(items) => { + for item in items { + match item { + crate::model::rc::AsIdOrRange::Id(id) => v.push((*id, *id)), + crate::model::rc::AsIdOrRange::Range { min, max } => v.push((*min, *max)), + } + } + } + } + v.sort_by_key(|(a, _b)| *a); + merge_as_intervals(&v) +} + +fn merge_as_intervals(v: &[(u32, u32)]) -> Vec<(u32, u32)> { + let mut out: Vec<(u32, u32)> = Vec::new(); + for (min, max) in v { + let Some(last) = out.last_mut() else { + out.push((*min, *max)); + continue; + }; + if *min <= last.1.saturating_add(1) { + last.1 = last.1.max(*max); + continue; + } + out.push((*min, *max)); + } + out +} + +fn as_interval_is_covered(parent: &[(u32, u32)], min: u32, max: u32) -> bool { + for (pmin, pmax) in parent { + if *pmin <= min && max <= *pmax { + return true; + } + if *pmin > min { + break; + } + } + false +} diff --git a/src/validation/ca_path/tests.rs b/src/validation/ca_path/tests.rs new file mode 100644 index 0000000..3b7d971 --- /dev/null +++ b/src/validation/ca_path/tests.rs @@ -0,0 +1,913 @@ +// CA path resource and certificate validation tests. + +use super::*; +use crate::model::common::X509NameDer; +use crate::model::oid::OID_CP_IPADDR_ASNUMBER; +use crate::model::rc::{ + Afi, AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily, + IpAddressOrRange, IpPrefix, IpResourceSet, +}; +use crate::model::rc::{ + BasicConstraintsProfile, CertificatePoliciesProfile, RcExtensions, ResourceCertKind, + ResourceCertificate, RpkixTbsCertificate, +}; +use der_parser::num_bigint::BigUint; +use std::process::Command; +fn dummy_cert( + kind: ResourceCertKind, + subject_dn: &str, + issuer_dn: &str, + ski: Option>, + aki: Option>, + aia: Option>, + crldp: Option>, +) -> ResourceCertificate { + let aia = aia.map(|v| v.into_iter().map(|s| s.to_string()).collect::>()); + let crldp = crldp.map(|v| v.into_iter().map(|s| s.to_string()).collect::>()); + + ResourceCertificate { + raw_der: Vec::new(), + kind, + tbs: RpkixTbsCertificate { + version: 2, + serial_number: BigUint::from(1u8), + signature_algorithm: "1.2.840.113549.1.1.11".to_string(), + issuer_name: X509NameDer(issuer_dn.as_bytes().to_vec()), + subject_name: X509NameDer(subject_dn.as_bytes().to_vec()), + validity_not_before: time::OffsetDateTime::UNIX_EPOCH, + validity_not_after: time::OffsetDateTime::UNIX_EPOCH, + subject_public_key_info: Vec::new(), + extensions: RcExtensions { + basic_constraints_ca: kind == ResourceCertKind::Ca, + basic_constraints: (kind == ResourceCertKind::Ca).then_some( + BasicConstraintsProfile { + ca: true, + critical: true, + path_len_constraint: None, + }, + ), + subject_key_identifier: ski, + authority_key_identifier: aki, + crl_distribution_points_uris: crldp, + ca_issuers_uris: aia, + subject_info_access: None, + certificate_policies_oid: (kind == ResourceCertKind::Ca) + .then_some(OID_CP_IPADDR_ASNUMBER.to_string()), + certificate_policies: (kind == ResourceCertKind::Ca).then_some( + CertificatePoliciesProfile { + policy_oid: OID_CP_IPADDR_ASNUMBER.to_string(), + qualifier_oids: Vec::new(), + }, + ), + extension_oids: Vec::new(), + ip_resources: None, + as_resources: None, + }, + }, + } +} + +fn openssl_available() -> bool { + Command::new("openssl") + .arg("version") + .output() + .map(|o| o.status.success()) + .unwrap_or(false) +} + +fn write_cert_der_with_addext(dir: &std::path::Path, addext: Option<&str>) -> Vec { + assert!(openssl_available(), "openssl is required for this test"); + let key = dir.join("k.pem"); + let cert = dir.join("c.pem"); + let der = dir.join("c.der"); + + let mut cmd = Command::new("openssl"); + cmd.arg("req") + .arg("-x509") + .arg("-newkey") + .arg("rsa:2048") + .arg("-nodes") + .arg("-keyout") + .arg(&key) + .arg("-subj") + .arg("/CN=ku") + .arg("-days") + .arg("1") + .arg("-out") + .arg(&cert); + if let Some(ext) = addext { + cmd.arg("-addext").arg(ext); + } + let out = cmd.output().expect("openssl req"); + assert!( + out.status.success(), + "openssl req failed: {}", + String::from_utf8_lossy(&out.stderr) + ); + + let out = Command::new("openssl") + .arg("x509") + .arg("-in") + .arg(&cert) + .arg("-outform") + .arg("DER") + .arg("-out") + .arg(&der) + .output() + .expect("openssl x509"); + assert!( + out.status.success(), + "openssl x509 failed: {}", + String::from_utf8_lossy(&out.stderr) + ); + std::fs::read(&der).expect("read der") +} + +fn gen_issuer_and_child_der(dir: &std::path::Path) -> (Vec, Vec, Vec) { + assert!(openssl_available(), "openssl is required for this test"); + let issuer_key = dir.join("issuer.key"); + let issuer_csr = dir.join("issuer.csr"); + let issuer_pem = dir.join("issuer.pem"); + let issuer_der = dir.join("issuer.der"); + + let child_key = dir.join("child.key"); + let child_csr = dir.join("child.csr"); + let child_pem = dir.join("child.pem"); + let child_der = dir.join("child.der"); + + let other_key = dir.join("other.key"); + let other_csr = dir.join("other.csr"); + let other_pem = dir.join("other.pem"); + let other_der = dir.join("other.der"); + + let run = |cmd: &mut Command| { + let out = cmd.output().expect("run openssl"); + assert!( + out.status.success(), + "command failed: {:?}\nstderr={}", + cmd, + String::from_utf8_lossy(&out.stderr) + ); + }; + + // Issuer self-signed. + run(Command::new("openssl") + .args(["genrsa", "-out"]) + .arg(&issuer_key) + .arg("2048")); + run(Command::new("openssl") + .args(["req", "-new", "-key"]) + .arg(&issuer_key) + .args(["-subj", "/CN=issuer", "-out"]) + .arg(&issuer_csr)); + run(Command::new("openssl") + .args(["x509", "-req", "-in"]) + .arg(&issuer_csr) + .args(["-signkey"]) + .arg(&issuer_key) + .args(["-days", "1", "-out"]) + .arg(&issuer_pem)); + run(Command::new("openssl") + .args(["x509", "-in"]) + .arg(&issuer_pem) + .args(["-outform", "DER", "-out"]) + .arg(&issuer_der)); + + // Child signed by issuer. + run(Command::new("openssl") + .args(["genrsa", "-out"]) + .arg(&child_key) + .arg("2048")); + run(Command::new("openssl") + .args(["req", "-new", "-key"]) + .arg(&child_key) + .args(["-subj", "/CN=child", "-out"]) + .arg(&child_csr)); + run(Command::new("openssl") + .args(["x509", "-req", "-in"]) + .arg(&child_csr) + .args(["-CA"]) + .arg(&issuer_pem) + .args(["-CAkey"]) + .arg(&issuer_key) + .args(["-CAcreateserial", "-days", "1", "-out"]) + .arg(&child_pem)); + run(Command::new("openssl") + .args(["x509", "-in"]) + .arg(&child_pem) + .args(["-outform", "DER", "-out"]) + .arg(&child_der)); + + // Other self-signed issuer. + run(Command::new("openssl") + .args(["genrsa", "-out"]) + .arg(&other_key) + .arg("2048")); + run(Command::new("openssl") + .args(["req", "-new", "-key"]) + .arg(&other_key) + .args(["-subj", "/CN=other", "-out"]) + .arg(&other_csr)); + run(Command::new("openssl") + .args(["x509", "-req", "-in"]) + .arg(&other_csr) + .args(["-signkey"]) + .arg(&other_key) + .args(["-days", "1", "-out"]) + .arg(&other_pem)); + run(Command::new("openssl") + .args(["x509", "-in"]) + .arg(&other_pem) + .args(["-outform", "DER", "-out"]) + .arg(&other_der)); + + ( + std::fs::read(&issuer_der).expect("read issuer der"), + std::fs::read(&child_der).expect("read child der"), + std::fs::read(&other_der).expect("read other der"), + ) +} + +#[test] +fn resolve_child_ip_resources_rejects_inherit_without_parent_effective_resources() { + let child = IpResourceSet { + families: vec![IpAddressFamily { + afi: Afi::Ipv4, + choice: IpAddressChoice::Inherit, + }], + }; + let err = resolve_child_ip_resources(Some(&child), None).unwrap_err(); + assert!(matches!(err, CaPathError::InheritWithoutParentResources)); +} + +#[test] +fn resolve_child_ip_resources_rejects_non_inherit_without_parent_effective_resources() { + let child = IpResourceSet { + families: vec![IpAddressFamily { + afi: Afi::Ipv4, + choice: IpAddressChoice::AddressesOrRanges(vec![]), + }], + }; + let err = resolve_child_ip_resources(Some(&child), None).unwrap_err(); + assert!(matches!(err, CaPathError::ResourcesNotSubset)); +} + +#[test] +fn ip_resources_by_afi_items_rejects_inherit_families() { + let parent = IpResourceSet { + families: vec![IpAddressFamily { + afi: Afi::Ipv6, + choice: IpAddressChoice::Inherit, + }], + }; + let err = ip_resources_by_afi_items(&parent).unwrap_err(); + assert!(matches!(err, CaPathError::InheritWithoutParentResources)); +} + +#[test] +fn resolve_child_as_resources_rejects_inherit_without_parent_effective_resources() { + let child = AsResourceSet { + asnum: Some(AsIdentifierChoice::Inherit), + rdi: None, + }; + let err = resolve_child_as_resources(Some(&child), None).unwrap_err(); + assert!(matches!(err, CaPathError::InheritWithoutParentResources)); +} + +#[test] +fn validation_update_03_intersects_overclaiming_child_ip_resources() { + let issuer = IpResourceSet { + families: vec![IpAddressFamily { + afi: Afi::Ipv4, + choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(IpPrefix { + afi: Afi::Ipv4, + prefix_len: 24, + addr: vec![10, 0, 0, 0], + })]), + }], + }; + let child = IpResourceSet { + families: vec![IpAddressFamily { + afi: Afi::Ipv4, + choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(IpPrefix { + afi: Afi::Ipv4, + prefix_len: 23, + addr: vec![10, 0, 0, 0], + })]), + }], + }; + let index = IssuerEffectiveResourcesIndex::from_effective_resources(Some(&issuer), None) + .expect("index"); + let resolved = resolve_child_resources( + Some(&child), + Some(&issuer), + None, + None, + &index, + ResourceValidationMode::ValidationUpdate03, + ) + .expect("vrs resolution"); + assert!(resolved.warnings.ip_reduced_by_vrs); + assert!(!resolved.warnings.ip_vrs_empty); + let effective = resolved.effective_ip_resources.expect("effective ip"); + assert!(effective.families[0].contains_prefix(&IpPrefix { + afi: Afi::Ipv4, + prefix_len: 24, + addr: vec![10, 0, 0, 0], + })); + assert!(!effective.families[0].contains_prefix(&IpPrefix { + afi: Afi::Ipv4, + prefix_len: 24, + addr: vec![10, 0, 1, 0], + })); +} + +#[test] +fn validation_update_03_keeps_empty_vrs_instead_of_rejecting_child_ca() { + let issuer = IpResourceSet { + families: vec![IpAddressFamily { + afi: Afi::Ipv4, + choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(IpPrefix { + afi: Afi::Ipv4, + prefix_len: 24, + addr: vec![10, 0, 0, 0], + })]), + }], + }; + let child = IpResourceSet { + families: vec![IpAddressFamily { + afi: Afi::Ipv4, + choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(IpPrefix { + afi: Afi::Ipv4, + prefix_len: 24, + addr: vec![192, 0, 2, 0], + })]), + }], + }; + let index = IssuerEffectiveResourcesIndex::from_effective_resources(Some(&issuer), None) + .expect("index"); + let strict_err = resolve_child_resources( + Some(&child), + Some(&issuer), + None, + None, + &index, + ResourceValidationMode::Rfc6487, + ) + .unwrap_err(); + assert!(matches!(strict_err, CaPathError::ResourcesNotSubset)); + + let resolved = resolve_child_resources( + Some(&child), + Some(&issuer), + None, + None, + &index, + ResourceValidationMode::ValidationUpdate03, + ) + .expect("vrs resolution"); + assert!(resolved.warnings.ip_reduced_by_vrs); + assert!(resolved.warnings.ip_vrs_empty); + assert!( + resolved + .effective_ip_resources + .expect("effective ip") + .families + .is_empty() + ); +} + +#[test] +fn validation_update_03_intersects_overclaiming_child_as_resources() { + let issuer = AsResourceSet { + asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![ + AsIdOrRange::Range { + min: 64500, + max: 64510, + }, + ])), + rdi: None, + }; + let child = AsResourceSet { + asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![ + AsIdOrRange::Range { + min: 64505, + max: 64520, + }, + ])), + rdi: None, + }; + let index = IssuerEffectiveResourcesIndex::from_effective_resources(None, Some(&issuer)) + .expect("index"); + let resolved = resolve_child_resources( + None, + None, + Some(&child), + Some(&issuer), + &index, + ResourceValidationMode::ValidationUpdate03, + ) + .expect("vrs resolution"); + assert!(resolved.warnings.as_reduced_by_vrs); + assert!(!resolved.warnings.as_vrs_empty); + let effective = resolved.effective_as_resources.expect("effective as"); + assert_eq!( + effective.asnum, + Some(AsIdentifierChoice::AsIdsOrRanges(vec![ + AsIdOrRange::Range { + min: 64505, + max: 64510, + }, + ])) + ); +} + +#[test] +fn validation_update_03_records_all_resource_warning_summary_parts() { + let issuer_ip = IpResourceSet { + families: vec![IpAddressFamily { + afi: Afi::Ipv4, + choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(IpPrefix { + afi: Afi::Ipv4, + prefix_len: 24, + addr: vec![10, 0, 0, 0], + })]), + }], + }; + let child_ip = IpResourceSet { + families: vec![IpAddressFamily { + afi: Afi::Ipv4, + choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(IpPrefix { + afi: Afi::Ipv4, + prefix_len: 24, + addr: vec![192, 0, 2, 0], + })]), + }], + }; + let issuer_as = AsResourceSet { + asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![ + AsIdOrRange::Range { + min: 64500, + max: 64510, + }, + ])), + rdi: None, + }; + let child_as = AsResourceSet { + asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![ + AsIdOrRange::Range { + min: 64520, + max: 64530, + }, + ])), + rdi: None, + }; + let index = + IssuerEffectiveResourcesIndex::from_effective_resources(Some(&issuer_ip), Some(&issuer_as)) + .expect("index"); + + let resolved = resolve_child_resources( + Some(&child_ip), + Some(&issuer_ip), + Some(&child_as), + Some(&issuer_as), + &index, + ResourceValidationMode::ValidationUpdate03, + ) + .expect("vrs resolution"); + + assert!(resolved.warnings.ip_reduced_by_vrs); + assert!(resolved.warnings.ip_vrs_empty); + assert!(resolved.warnings.as_reduced_by_vrs); + assert!(resolved.warnings.as_vrs_empty); + assert_eq!( + resolved.warnings.summary(), + "ip_reduced_by_vrs,as_reduced_by_vrs,ip_vrs_empty,as_vrs_empty" + ); + assert!(!resolved.warnings.is_empty()); +} + +#[test] +fn child_aki_mismatch_is_rejected() { + let issuer = dummy_cert( + ResourceCertKind::Ca, + "CN=issuer", + "CN=issuer", + Some(vec![1]), + None, + None, + None, + ); + let child = dummy_cert( + ResourceCertKind::Ca, + "CN=child", + "CN=issuer", + Some(vec![2]), + Some(vec![9]), + Some(vec!["rsync://example.test/issuer.cer"]), + Some(vec!["rsync://example.test/issuer.crl"]), + ); + let err = validate_child_aki_matches_issuer_ski(&child, &issuer).unwrap_err(); + assert!(matches!(err, CaPathError::ChildAkiMismatch), "{err}"); +} + +#[test] +fn child_aia_missing_is_rejected() { + let _issuer = dummy_cert( + ResourceCertKind::Ca, + "CN=issuer", + "CN=issuer", + Some(vec![1]), + None, + None, + None, + ); + let child = dummy_cert( + ResourceCertKind::Ca, + "CN=child", + "CN=issuer", + Some(vec![2]), + Some(vec![1]), + None, + Some(vec!["rsync://example.test/issuer.crl"]), + ); + let err = validate_child_aia_points_to_issuer_uri(&child, "rsync://example.test/issuer.cer") + .unwrap_err(); + assert!(matches!(err, CaPathError::ChildAiaMissing), "{err}"); + + // Also cover issuer ski missing. + let issuer_missing_ski = dummy_cert( + ResourceCertKind::Ca, + "CN=issuer", + "CN=issuer", + None, + None, + None, + None, + ); + let err = validate_child_aki_matches_issuer_ski(&child, &issuer_missing_ski).unwrap_err(); + assert!(matches!(err, CaPathError::IssuerSkiMissing), "{err}"); +} + +#[test] +fn child_aia_issuer_uri_mismatch_is_rejected() { + let _issuer = dummy_cert( + ResourceCertKind::Ca, + "CN=issuer", + "CN=issuer", + Some(vec![1]), + None, + None, + None, + ); + let child = dummy_cert( + ResourceCertKind::Ca, + "CN=child", + "CN=issuer", + Some(vec![2]), + Some(vec![1]), + Some(vec!["rsync://example.test/other.cer"]), + Some(vec!["rsync://example.test/issuer.crl"]), + ); + let err = validate_child_aia_points_to_issuer_uri(&child, "rsync://example.test/issuer.cer") + .unwrap_err(); + assert!( + matches!(err, CaPathError::ChildAiaIssuerUriMismatch), + "{err}" + ); +} + +#[test] +fn child_crldp_mismatch_is_rejected() { + let issuer = dummy_cert( + ResourceCertKind::Ca, + "CN=issuer", + "CN=issuer", + Some(vec![1]), + None, + None, + None, + ); + let child = dummy_cert( + ResourceCertKind::Ca, + "CN=child", + "CN=issuer", + Some(vec![2]), + Some(vec![1]), + Some(vec!["rsync://example.test/issuer.cer"]), + None, + ); + let err = + validate_child_crldp_contains_issuer_crl_uri(&child, "rsync://example.test/issuer.crl") + .unwrap_err(); + assert!(matches!(err, CaPathError::ChildCrlDpMissing), "{err}"); + + let child = dummy_cert( + ResourceCertKind::Ca, + "CN=child", + "CN=issuer", + Some(vec![2]), + Some(vec![1]), + Some(vec!["rsync://example.test/issuer.cer"]), + Some(vec!["rsync://example.test/other.crl"]), + ); + let err = + validate_child_crldp_contains_issuer_crl_uri(&child, "rsync://example.test/issuer.crl") + .unwrap_err(); + assert!(matches!(err, CaPathError::ChildCrlDpUriMismatch), "{err}"); + + // Cover child AKI missing. + let child_missing_aki = dummy_cert( + ResourceCertKind::Ca, + "CN=child", + "CN=issuer", + Some(vec![2]), + None, + Some(vec!["rsync://example.test/issuer.cer"]), + Some(vec!["rsync://example.test/issuer.crl"]), + ); + let err = validate_child_aki_matches_issuer_ski(&child_missing_aki, &issuer).unwrap_err(); + assert!(matches!(err, CaPathError::ChildAkiMissing), "{err}"); +} + +#[test] +fn child_binding_checks_accept_when_matching() { + let issuer = dummy_cert( + ResourceCertKind::Ca, + "CN=issuer", + "CN=issuer", + Some(vec![1]), + None, + None, + None, + ); + let child = dummy_cert( + ResourceCertKind::Ca, + "CN=child", + "CN=issuer", + Some(vec![2]), + Some(vec![1]), + Some(vec!["rsync://example.test/issuer.cer"]), + Some(vec!["rsync://example.test/issuer.crl"]), + ); + validate_child_aki_matches_issuer_ski(&child, &issuer).expect("aki ok"); + validate_child_aia_points_to_issuer_uri(&child, "rsync://example.test/issuer.cer") + .expect("aia ok"); + validate_child_crldp_contains_issuer_crl_uri(&child, "rsync://example.test/issuer.crl") + .expect("crldp ok"); +} + +#[test] +fn validate_child_ca_key_usage_accepts_only_keycertsign_and_crlsign_critical() { + let td = tempfile::tempdir().expect("tempdir"); + let der = + write_cert_der_with_addext(td.path(), Some("keyUsage = critical, keyCertSign, cRLSign")); + let cert = parse_x509_cert(&der).expect("x509 parse ok"); + validate_child_ca_key_usage(&cert).expect("key usage ok"); +} + +#[test] +fn validate_child_ca_key_usage_rejects_missing_noncritical_and_invalid_bits() { + let td = tempfile::tempdir().expect("tempdir"); + let missing = write_cert_der_with_addext(td.path(), None); + let cert = parse_x509_cert(&missing).expect("x509 parse ok"); + let err = validate_child_ca_key_usage(&cert).unwrap_err(); + assert!(matches!(err, CaPathError::KeyUsageMissing), "{err}"); + + let td = tempfile::tempdir().expect("tempdir"); + let noncritical = + write_cert_der_with_addext(td.path(), Some("keyUsage = keyCertSign, cRLSign")); + let cert = parse_x509_cert(&noncritical).expect("x509 parse ok"); + let err = validate_child_ca_key_usage(&cert).unwrap_err(); + assert!(matches!(err, CaPathError::KeyUsageNotCritical), "{err}"); + + let td = tempfile::tempdir().expect("tempdir"); + let invalid = write_cert_der_with_addext( + td.path(), + Some("keyUsage = critical, keyCertSign, cRLSign, digitalSignature"), + ); + let cert = parse_x509_cert(&invalid).expect("x509 parse ok"); + let err = validate_child_ca_key_usage(&cert).unwrap_err(); + assert!(matches!(err, CaPathError::KeyUsageInvalidBits), "{err}"); +} + +#[test] +fn verify_cert_signature_with_issuer_accepts_valid_chain_and_rejects_wrong_issuer() { + let td = tempfile::tempdir().expect("tempdir"); + let (issuer, child, other) = gen_issuer_and_child_der(td.path()); + let issuer_cert = parse_x509_cert(&issuer).expect("x509 parse issuer"); + let child_cert = parse_x509_cert(&child).expect("x509 parse child"); + verify_child_signature(&child_cert, &issuer_cert.tbs_certificate.subject_pki) + .expect("signature ok"); + let other_cert = parse_x509_cert(&other).expect("x509 parse other"); + let err = + verify_child_signature(&child_cert, &other_cert.tbs_certificate.subject_pki).unwrap_err(); + assert!( + matches!(err, CaPathError::ChildSignatureInvalid(_)), + "{err}" + ); +} + +#[test] +fn issuer_effective_resources_index_and_indexed_resolvers_cover_success_and_failure_paths() { + use crate::model::rc::{AsIdOrRange, IpPrefix}; + + let parent_ip = IpResourceSet { + families: vec![IpAddressFamily { + afi: Afi::Ipv4, + choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(IpPrefix { + afi: Afi::Ipv4, + prefix_len: 8, + addr: vec![10, 0, 0, 0], + })]), + }], + }; + let parent_as = AsResourceSet { + asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![ + AsIdOrRange::Range { + min: 64500, + max: 64599, + }, + ])), + rdi: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id( + 65000, + )])), + }; + let idx = + IssuerEffectiveResourcesIndex::from_effective_resources(Some(&parent_ip), Some(&parent_as)) + .expect("index builds"); + assert_eq!( + idx.parent_ip_by_afi_items.as_ref().map(|v| v.len()), + Some(1) + ); + assert_eq!(idx.parent_ip_merged_intervals.len(), 1); + assert_eq!( + idx.parent_asnum_intervals.as_ref().map(|v| v.len()), + Some(1) + ); + assert_eq!(idx.parent_rdi_intervals.as_ref().map(|v| v.len()), Some(1)); + + let child_ip_subset = IpResourceSet { + families: vec![IpAddressFamily { + afi: Afi::Ipv4, + choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(IpPrefix { + afi: Afi::Ipv4, + prefix_len: 16, + addr: vec![10, 1, 0, 0], + })]), + }], + }; + assert!( + resolve_child_ip_resources_indexed( + Some(&child_ip_subset), + Some(&parent_ip), + idx.parent_ip_by_afi_items.as_ref(), + &idx.parent_ip_merged_intervals, + ) + .expect("subset should resolve") + .is_some() + ); + + let child_ip_bad = IpResourceSet { + families: vec![IpAddressFamily { + afi: Afi::Ipv4, + choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(IpPrefix { + afi: Afi::Ipv4, + prefix_len: 16, + addr: vec![11, 0, 0, 0], + })]), + }], + }; + let err = resolve_child_ip_resources_indexed( + Some(&child_ip_bad), + Some(&parent_ip), + idx.parent_ip_by_afi_items.as_ref(), + &idx.parent_ip_merged_intervals, + ) + .unwrap_err(); + assert!(matches!(err, CaPathError::ResourcesNotSubset)); + + let child_as_subset = AsResourceSet { + asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id( + 64542, + )])), + rdi: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id( + 65000, + )])), + }; + assert!( + resolve_child_as_resources_indexed( + Some(&child_as_subset), + Some(&parent_as), + idx.parent_asnum_intervals.as_deref(), + idx.parent_rdi_intervals.as_deref(), + ) + .expect("subset as resolves") + .is_some() + ); + + let child_as_bad = AsResourceSet { + asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id( + 65123, + )])), + rdi: None, + }; + let err = resolve_child_as_resources_indexed( + Some(&child_as_bad), + Some(&parent_as), + idx.parent_asnum_intervals.as_deref(), + idx.parent_rdi_intervals.as_deref(), + ) + .unwrap_err(); + assert!(matches!(err, CaPathError::ResourcesNotSubset)); +} + +#[test] +fn resolve_child_ip_and_as_resources_success_paths() { + use crate::model::rc::{AsIdOrRange, IpAddressOrRange, IpPrefix}; + + let parent_ip = IpResourceSet { + families: vec![IpAddressFamily { + afi: Afi::Ipv4, + choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(IpPrefix { + afi: Afi::Ipv4, + prefix_len: 8, + addr: vec![10, 0, 0, 0], + })]), + }], + }; + + let child_ip_inherit = IpResourceSet { + families: vec![IpAddressFamily { + afi: Afi::Ipv4, + choice: IpAddressChoice::Inherit, + }], + }; + let eff = resolve_child_ip_resources(Some(&child_ip_inherit), Some(&parent_ip)) + .expect("inherit resolves") + .expect("some ip"); + assert_eq!(eff.families.len(), 1); + assert!(matches!( + eff.families[0].choice, + IpAddressChoice::AddressesOrRanges(_) + )); + + let child_ip_subset = IpResourceSet { + families: vec![IpAddressFamily { + afi: Afi::Ipv4, + choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(IpPrefix { + afi: Afi::Ipv4, + prefix_len: 16, + addr: vec![10, 1, 0, 0], + })]), + }], + }; + resolve_child_ip_resources(Some(&child_ip_subset), Some(&parent_ip)) + .expect("subset ok") + .expect("some"); + + let child_ip_bad = IpResourceSet { + families: vec![IpAddressFamily { + afi: Afi::Ipv4, + choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(IpPrefix { + afi: Afi::Ipv4, + prefix_len: 16, + addr: vec![11, 0, 0, 0], + })]), + }], + }; + let err = resolve_child_ip_resources(Some(&child_ip_bad), Some(&parent_ip)).unwrap_err(); + assert!(matches!(err, CaPathError::ResourcesNotSubset), "{err}"); + + let parent_as = AsResourceSet { + asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![ + AsIdOrRange::Range { min: 1, max: 100 }, + ])), + rdi: None, + }; + let child_as_inherit = AsResourceSet { + asnum: Some(AsIdentifierChoice::Inherit), + rdi: None, + }; + let eff_as = resolve_child_as_resources(Some(&child_as_inherit), Some(&parent_as)) + .expect("inherit as") + .expect("some"); + assert_eq!(eff_as.asnum, parent_as.asnum); + + let child_as_subset = AsResourceSet { + asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(50)])), + rdi: None, + }; + resolve_child_as_resources(Some(&child_as_subset), Some(&parent_as)) + .expect("subset as") + .expect("some"); + + let child_as_bad = AsResourceSet { + asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id( + 200, + )])), + rdi: None, + }; + let err = resolve_child_as_resources(Some(&child_as_bad), Some(&parent_as)).unwrap_err(); + assert!(matches!(err, CaPathError::ResourcesNotSubset), "{err}"); +} diff --git a/src/validation/ca_path/types_and_validation.rs b/src/validation/ca_path/types_and_validation.rs new file mode 100644 index 0000000..e44e740 --- /dev/null +++ b/src/validation/ca_path/types_and_validation.rs @@ -0,0 +1,464 @@ +// Resource certificate path types and public validation entry points. + +use crate::model::common::BigUnsigned; +use crate::model::crl::{CrlDecodeError, CrlVerifyError, RpkixCrl}; +use crate::model::oid::OID_KEY_USAGE_RAW; +use crate::model::rc::{ + AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpResourceSet, ResourceCertKind, + ResourceCertificate, ResourceCertificateDecodeError, ResourceCertificateProfileError, + ResourceCertificateRole, +}; +use crate::validation::policy::ResourceValidationMode; +use x509_parser::prelude::{FromDer, X509Certificate}; + +use crate::validation::x509_name::x509_names_equivalent; +use std::collections::{BTreeMap, HashMap, HashSet}; +use x509_parser::x509::SubjectPublicKeyInfo; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ValidatedSubordinateCa { + pub child_ca: ResourceCertificate, + pub issuer_ca: ResourceCertificate, + pub issuer_crl: RpkixCrl, + pub effective_ip_resources: Option, + pub effective_as_resources: Option, + pub resource_warnings: ResourceValidationWarnings, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ValidatedSubordinateCaLite { + pub child_ca: ResourceCertificate, + pub effective_ip_resources: Option, + pub effective_as_resources: Option, + pub resource_warnings: ResourceValidationWarnings, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct ResourceValidationWarnings { + pub ip_reduced_by_vrs: bool, + pub as_reduced_by_vrs: bool, + pub ip_vrs_empty: bool, + pub as_vrs_empty: bool, +} + +impl ResourceValidationWarnings { + pub fn is_empty(&self) -> bool { + !self.ip_reduced_by_vrs + && !self.as_reduced_by_vrs + && !self.ip_vrs_empty + && !self.as_vrs_empty + } + + pub fn summary(&self) -> String { + let mut parts = Vec::new(); + if self.ip_reduced_by_vrs { + parts.push("ip_reduced_by_vrs"); + } + if self.as_reduced_by_vrs { + parts.push("as_reduced_by_vrs"); + } + if self.ip_vrs_empty { + parts.push("ip_vrs_empty"); + } + if self.as_vrs_empty { + parts.push("as_vrs_empty"); + } + parts.join(",") + } +} + +#[derive(Clone, Debug, Default)] +pub struct IssuerEffectiveResourcesIndex { + parent_ip_by_afi_items: + Option>>, + parent_ip_merged_intervals: HashMap, Vec)>>, + parent_asnum_intervals: Option>, + parent_rdi_intervals: Option>, +} + +impl IssuerEffectiveResourcesIndex { + pub fn from_effective_resources( + issuer_effective_ip: Option<&IpResourceSet>, + issuer_effective_as: Option<&AsResourceSet>, + ) -> Result { + let parent_ip_by_afi_items = issuer_effective_ip + .map(ip_resources_by_afi_items) + .transpose()?; + + let parent_ip_merged_intervals = issuer_effective_ip + .map(ip_resources_to_merged_intervals_by_afi) + .unwrap_or_default(); + + let parent_asnum_intervals = issuer_effective_as + .and_then(|resources| resources.asnum.as_ref().map(as_choice_to_merged_intervals)); + let parent_rdi_intervals = issuer_effective_as + .and_then(|resources| resources.rdi.as_ref().map(as_choice_to_merged_intervals)); + + Ok(Self { + parent_ip_by_afi_items, + parent_ip_merged_intervals, + parent_asnum_intervals, + parent_rdi_intervals, + }) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum CaPathError { + #[error("child CA certificate decode failed: {0} (RFC 6487 §4; RFC 5280 §4.1)")] + ChildDecode(#[from] ResourceCertificateDecodeError), + + #[error("issuer CA certificate decode failed: {0} (RFC 6487 §4; RFC 5280 §4.1)")] + IssuerDecode(ResourceCertificateDecodeError), + + #[error("child CA certificate profile validation failed: {0} (RFC 6487 §4.8)")] + ChildProfile(ResourceCertificateProfileError), + + #[error("issuer CA certificate profile validation failed: {0} (RFC 6487 §4.8)")] + IssuerProfile(ResourceCertificateProfileError), + + #[error("issuer CRL decode failed: {0} (RFC 6487 §5; RFC 9829 §3.1; RFC 5280 §5.1)")] + CrlDecode(#[from] CrlDecodeError), + + #[error( + "child certificate must be a CA resource certificate (RFC 6487 §4.8.1; RFC 5280 §4.2.1.9)" + )] + ChildNotCa, + + #[error( + "issuer certificate must be a CA resource certificate (RFC 6487 §4.8.1; RFC 5280 §4.2.1.9)" + )] + IssuerNotCa, + + #[error( + "child issuer DN does not match issuer CA subject DN: child.issuer={child_issuer_dn} issuer.subject={issuer_subject_dn} (RFC 5280 §6.1)" + )] + IssuerSubjectMismatch { + child_issuer_dn: String, + issuer_subject_dn: String, + }, + + #[error("child CA certificate signature verification failed: {0} (RFC 5280 §6.1)")] + ChildSignatureInvalid(String), + + #[error("issuer SubjectPublicKeyInfo parse error: {0} (RFC 5280 §4.1.2.7)")] + IssuerSpkiParse(String), + + #[error( + "trailing bytes after issuer SubjectPublicKeyInfo DER: {0} bytes (DER; RFC 5280 §4.1.2.7)" + )] + IssuerSpkiTrailingBytes(usize), + + #[error("certificate not valid at validation_time (RFC 5280 §4.1.2.5; RFC 5280 §6.1)")] + CertificateNotValidAtTime, + + #[error("child CA KeyUsage extension missing (RFC 6487 §4.8.4; RFC 5280 §4.2.1.3)")] + KeyUsageMissing, + + #[error("child CA KeyUsage criticality must be critical (RFC 6487 §4.8.4; RFC 5280 §4.2.1.3)")] + KeyUsageNotCritical, + + #[error("child CA KeyUsage must have only keyCertSign and cRLSign set (RFC 6487 §4.8.4)")] + KeyUsageInvalidBits, + + #[error( + "CRL signature/binding verification failed: {0} (RFC 5280 §6.3.3; RFC 6487 §5; RFC 9829 §3.1)" + )] + CrlVerify(#[from] CrlVerifyError), + + #[error( + "CRL not valid at validation_time (RFC 5280 §6.3.3(g); RFC 5280 §5.1.2.4-§5.1.2.5; RFC 6487 §5)" + )] + CrlNotValidAtTime, + + #[error("child CA certificate is revoked by issuer CRL (RFC 5280 §6.3.3; RFC 6487 §5)")] + ChildRevoked, + + #[error( + "child CA certificate must contain at least one RFC 3779 resource extension (IP or AS) (RFC 6487 §4.8.10-§4.8.11)" + )] + ResourcesMissing, + + #[error( + "resource extension inheritance cannot be resolved (parent missing resources) (RFC 6487 §7.2)" + )] + InheritWithoutParentResources, + + #[error("child CA resources are not a subset of issuer resources (RFC 6487 §7.2)")] + ResourcesNotSubset, + + #[error("issuer CA subjectKeyIdentifier missing (RFC 6487 §4.8.2)")] + IssuerSkiMissing, + + #[error("child CA authorityKeyIdentifier missing (RFC 6487 §4.8.3; RFC 5280 §4.2.1.1)")] + ChildAkiMissing, + + #[error( + "child CA authorityKeyIdentifier does not match issuer subjectKeyIdentifier (RFC 6487 §4.8.3)" + )] + ChildAkiMismatch, + + #[error("child CA authorityInfoAccess missing (RFC 6487 §4.8.7; RFC 5280 §4.2.2.1)")] + ChildAiaMissing, + + #[error( + "child CA authorityInfoAccess does not reference issuer certificate rsync URI (RFC 6487 §4.8.7)" + )] + ChildAiaIssuerUriMismatch, + + #[error("child CA CRLDistributionPoints missing (RFC 6487 §4.8.6; RFC 5280 §4.2.1.13)")] + ChildCrlDpMissing, + + #[error( + "child CA CRLDistributionPoints does not reference issuer CRL rsync URI (RFC 6487 §4.8.6)" + )] + ChildCrlDpUriMismatch, +} + +pub fn validate_subordinate_ca_cert( + child_ca_der: &[u8], + issuer_ca_der: &[u8], + issuer_crl_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, + issuer_crl_rsync_uri: &str, + issuer_effective_ip: Option<&IpResourceSet>, + issuer_effective_as: Option<&AsResourceSet>, + validation_time: time::OffsetDateTime, +) -> Result { + validate_subordinate_ca_cert_with_resource_validation_mode( + child_ca_der, + issuer_ca_der, + issuer_crl_der, + issuer_ca_rsync_uri, + issuer_crl_rsync_uri, + issuer_effective_ip, + issuer_effective_as, + validation_time, + ResourceValidationMode::Rfc6487, + ) +} + +pub fn validate_subordinate_ca_cert_with_resource_validation_mode( + child_ca_der: &[u8], + issuer_ca_der: &[u8], + issuer_crl_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, + issuer_crl_rsync_uri: &str, + issuer_effective_ip: Option<&IpResourceSet>, + issuer_effective_as: Option<&AsResourceSet>, + validation_time: time::OffsetDateTime, + resource_validation_mode: ResourceValidationMode, +) -> Result { + let child_ca = ResourceCertificate::decode_der(child_ca_der)?; + if child_ca.kind != ResourceCertKind::Ca { + return Err(CaPathError::ChildNotCa); + } + child_ca + .validate_rfc6487_profile(ResourceCertificateRole::Ca) + .map_err(CaPathError::ChildProfile)?; + + let issuer_ca = + ResourceCertificate::decode_der(issuer_ca_der).map_err(CaPathError::IssuerDecode)?; + if issuer_ca.kind != ResourceCertKind::Ca { + return Err(CaPathError::IssuerNotCa); + } + issuer_ca + .validate_rfc6487_profile(ResourceCertificateRole::Ca) + .map_err(CaPathError::IssuerProfile)?; + let issuer_spki = parse_subject_pki_from_der(&issuer_ca.tbs.subject_public_key_info)?; + + if !x509_names_equivalent(&child_ca.tbs.issuer_name, &issuer_ca.tbs.subject_name) { + return Err(CaPathError::IssuerSubjectMismatch { + child_issuer_dn: child_ca.tbs.issuer_name.to_string(), + issuer_subject_dn: issuer_ca.tbs.subject_name.to_string(), + }); + } + + validate_child_aki_matches_issuer_ski(&child_ca, &issuer_ca)?; + if let Some(expected_issuer_uri) = issuer_ca_rsync_uri { + validate_child_aia_points_to_issuer_uri(&child_ca, expected_issuer_uri)?; + } + validate_child_crldp_contains_issuer_crl_uri(&child_ca, issuer_crl_rsync_uri)?; + + if !time_within_validity( + validation_time, + child_ca.tbs.validity_not_before, + child_ca.tbs.validity_not_after, + ) || !time_within_validity( + validation_time, + issuer_ca.tbs.validity_not_before, + issuer_ca.tbs.validity_not_after, + ) { + return Err(CaPathError::CertificateNotValidAtTime); + } + + let child_x509 = parse_x509_cert(child_ca_der)?; + verify_child_signature(&child_x509, &issuer_spki)?; + validate_child_ca_key_usage(&child_x509)?; + + let issuer_crl = RpkixCrl::decode_der(issuer_crl_der)?; + issuer_crl.verify_signature_with_issuer_certificate_der(issuer_ca_der)?; + if !crl_valid_at_time(&issuer_crl, validation_time) { + return Err(CaPathError::CrlNotValidAtTime); + } + + if is_serial_revoked_by_crl(&child_ca, &issuer_crl) { + return Err(CaPathError::ChildRevoked); + } + + let ResourceResolution { + effective_ip_resources, + effective_as_resources, + warnings: resource_warnings, + } = resolve_child_resources( + child_ca.tbs.extensions.ip_resources.as_ref(), + issuer_effective_ip, + child_ca.tbs.extensions.as_resources.as_ref(), + issuer_effective_as, + &IssuerEffectiveResourcesIndex::from_effective_resources( + issuer_effective_ip, + issuer_effective_as, + )?, + resource_validation_mode, + )?; + if effective_ip_resources.is_none() && effective_as_resources.is_none() { + return Err(CaPathError::ResourcesMissing); + } + + Ok(ValidatedSubordinateCa { + child_ca, + issuer_ca, + issuer_crl, + effective_ip_resources, + effective_as_resources, + resource_warnings, + }) +} + +/// Validate a subordinate child CA using *pre-decoded issuer CA* and *pre-decoded+verified issuer CRL*. +/// +/// This avoids repeating issuer CA decode and issuer CRL decode+signature verification for every +/// child CA certificate discovered in a publication point. +pub fn validate_subordinate_ca_cert_with_prevalidated_issuer( + child_ca_der: &[u8], + child_ca: ResourceCertificate, + issuer_ca: &ResourceCertificate, + issuer_spki: &SubjectPublicKeyInfo<'_>, + issuer_crl: &RpkixCrl, + issuer_crl_revoked_serials: &HashSet>, + issuer_ca_rsync_uri: Option<&str>, + issuer_crl_rsync_uri: &str, + issuer_effective_ip: Option<&IpResourceSet>, + issuer_effective_as: Option<&AsResourceSet>, + validation_time: time::OffsetDateTime, +) -> Result { + let issuer_resources_index = IssuerEffectiveResourcesIndex::from_effective_resources( + issuer_effective_ip, + issuer_effective_as, + )?; + validate_subordinate_ca_cert_with_prevalidated_issuer_and_resources( + child_ca_der, + child_ca, + issuer_ca, + issuer_spki, + issuer_crl, + issuer_crl_revoked_serials, + issuer_ca_rsync_uri, + issuer_crl_rsync_uri, + issuer_effective_ip, + issuer_effective_as, + &issuer_resources_index, + validation_time, + ResourceValidationMode::Rfc6487, + ) +} + +pub fn validate_subordinate_ca_cert_with_prevalidated_issuer_and_resources( + child_ca_der: &[u8], + child_ca: ResourceCertificate, + issuer_ca: &ResourceCertificate, + issuer_spki: &SubjectPublicKeyInfo<'_>, + issuer_crl: &RpkixCrl, + issuer_crl_revoked_serials: &HashSet>, + issuer_ca_rsync_uri: Option<&str>, + issuer_crl_rsync_uri: &str, + issuer_effective_ip: Option<&IpResourceSet>, + issuer_effective_as: Option<&AsResourceSet>, + issuer_resources_index: &IssuerEffectiveResourcesIndex, + validation_time: time::OffsetDateTime, + resource_validation_mode: ResourceValidationMode, +) -> Result { + if child_ca.kind != ResourceCertKind::Ca { + return Err(CaPathError::ChildNotCa); + } + if issuer_ca.kind != ResourceCertKind::Ca { + return Err(CaPathError::IssuerNotCa); + } + child_ca + .validate_rfc6487_profile(ResourceCertificateRole::Ca) + .map_err(CaPathError::ChildProfile)?; + issuer_ca + .validate_rfc6487_profile(ResourceCertificateRole::Ca) + .map_err(CaPathError::IssuerProfile)?; + + if !x509_names_equivalent(&child_ca.tbs.issuer_name, &issuer_ca.tbs.subject_name) { + return Err(CaPathError::IssuerSubjectMismatch { + child_issuer_dn: child_ca.tbs.issuer_name.to_string(), + issuer_subject_dn: issuer_ca.tbs.subject_name.to_string(), + }); + } + + validate_child_aki_matches_issuer_ski(&child_ca, issuer_ca)?; + if let Some(expected_issuer_uri) = issuer_ca_rsync_uri { + validate_child_aia_points_to_issuer_uri(&child_ca, expected_issuer_uri)?; + } + validate_child_crldp_contains_issuer_crl_uri(&child_ca, issuer_crl_rsync_uri)?; + + if !time_within_validity( + validation_time, + child_ca.tbs.validity_not_before, + child_ca.tbs.validity_not_after, + ) || !time_within_validity( + validation_time, + issuer_ca.tbs.validity_not_before, + issuer_ca.tbs.validity_not_after, + ) { + return Err(CaPathError::CertificateNotValidAtTime); + } + + let child_x509 = parse_x509_cert(child_ca_der)?; + verify_child_signature(&child_x509, issuer_spki)?; + validate_child_ca_key_usage(&child_x509)?; + + if !crl_valid_at_time(issuer_crl, validation_time) { + return Err(CaPathError::CrlNotValidAtTime); + } + + let serial = BigUnsigned::from_biguint(&child_ca.tbs.serial_number); + if issuer_crl_revoked_serials.contains(&serial.bytes_be) { + return Err(CaPathError::ChildRevoked); + } + + let ResourceResolution { + effective_ip_resources, + effective_as_resources, + warnings: resource_warnings, + } = resolve_child_resources( + child_ca.tbs.extensions.ip_resources.as_ref(), + issuer_effective_ip, + child_ca.tbs.extensions.as_resources.as_ref(), + issuer_effective_as, + issuer_resources_index, + resource_validation_mode, + )?; + if effective_ip_resources.is_none() && effective_as_resources.is_none() { + return Err(CaPathError::ResourcesMissing); + } + + Ok(ValidatedSubordinateCaLite { + child_ca, + effective_ip_resources, + effective_as_resources, + resource_warnings, + }) +} diff --git a/src/validation/cert_path.rs b/src/validation/cert_path.rs new file mode 100644 index 0000000..fecae04 --- /dev/null +++ b/src/validation/cert_path.rs @@ -0,0 +1,725 @@ +#![allow(clippy::too_many_arguments)] + +use crate::model::common::BigUnsigned; +use crate::model::crl::{CrlDecodeError, CrlVerifyError, RpkixCrl}; +use crate::model::rc::{ + ResourceCertKind, ResourceCertificate, ResourceCertificateDecodeError, + ResourceCertificateProfileError, ResourceCertificateRole, +}; +use crate::model::signed_object::{EeKeyUsageSummary, ResourceEeCertificate}; +use ring::signature; +use x509_parser::prelude::{FromDer, X509Certificate}; + +use crate::validation::x509_name::x509_names_equivalent; +use std::collections::HashSet; +use x509_parser::x509::SubjectPublicKeyInfo; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ValidatedEeCertPath { + pub ee: ResourceCertificate, + pub issuer_ca: ResourceCertificate, + pub issuer_crl: RpkixCrl, +} + +#[derive(Debug, thiserror::Error)] +pub enum CertPathError { + #[error("EE certificate decode failed: {0} (RFC 6487 §4; RFC 5280 §4.1)")] + EeDecode(#[from] ResourceCertificateDecodeError), + + #[error("issuer CA certificate decode failed: {0} (RFC 6487 §4; RFC 5280 §4.1)")] + IssuerDecode(ResourceCertificateDecodeError), + + #[error("issuer CRL decode failed: {0} (RFC 6487 §5; RFC 9829 §3.1; RFC 5280 §5.1)")] + CrlDecode(#[from] CrlDecodeError), + + #[error( + "issuer certificate must be a CA resource certificate (RFC 6487 §4.8.1; RFC 5280 §4.2.1.9)" + )] + IssuerNotCa, + + #[error("issuer CA certificate profile validation failed: {0} (RFC 6487 §4.8)")] + IssuerProfile(ResourceCertificateProfileError), + + #[error("EE certificate must be an EE resource certificate (RFC 6487 §4)")] + EeNotEe, + + #[error( + "EE issuer DN does not match issuer CA subject DN: ee.issuer={ee_issuer_dn} issuer.subject={issuer_subject_dn} (RFC 5280 §6.1)" + )] + IssuerSubjectMismatch { + ee_issuer_dn: String, + issuer_subject_dn: String, + }, + + #[error("EE certificate signature verification failed: {0} (RFC 5280 §6.1)")] + EeSignatureInvalid(String), + + #[error("issuer SubjectPublicKeyInfo parse error: {0} (RFC 5280 §4.1.2.7)")] + IssuerSpkiParse(String), + + #[error( + "trailing bytes after issuer SubjectPublicKeyInfo DER: {0} bytes (DER; RFC 5280 §4.1.2.7)" + )] + IssuerSpkiTrailingBytes(usize), + + #[error("EE KeyUsage extension missing (RFC 6487 §4.8.4; RFC 5280 §4.2.1.3)")] + KeyUsageMissing, + + #[error("EE KeyUsage criticality must be critical (RFC 6487 §4.8.4; RFC 5280 §4.2.1.3)")] + KeyUsageNotCritical, + + #[error("EE KeyUsage must have only digitalSignature set (RFC 6487 §4.8.4)")] + KeyUsageInvalidBits, + + #[error("EE KeyUsage parse failed: {0} (RFC 6487 §4.8.4; RFC 5280 §4.2.1.3)")] + KeyUsageParse(String), + + #[error("issuer CA subjectKeyIdentifier missing (RFC 6487 §4.8.2)")] + IssuerSkiMissing, + + #[error("EE authorityKeyIdentifier missing (RFC 6487 §4.8.3; RFC 5280 §4.2.1.1)")] + EeAkiMissing, + + #[error( + "EE authorityKeyIdentifier does not match issuer subjectKeyIdentifier (RFC 6487 §4.8.3)" + )] + EeAkiMismatch, + + #[error("EE authorityInfoAccess missing (RFC 6487 §4.8.7; RFC 5280 §4.2.2.1)")] + EeAiaMissing, + + #[error( + "EE authorityInfoAccess does not reference issuer certificate rsync URI (RFC 6487 §4.8.7)" + )] + EeAiaIssuerUriMismatch, + + #[error("EE CRLDistributionPoints missing (RFC 6487 §4.8.6; RFC 5280 §4.2.1.13)")] + EeCrlDpMissing, + + #[error("EE CRLDistributionPoints does not reference issuer CRL rsync URI (RFC 6487 §4.8.6)")] + EeCrlDpUriMismatch, + + #[error("certificate not valid at validation_time (RFC 5280 §4.1.2.5; RFC 5280 §6.1)")] + CertificateNotValidAtTime, + + #[error( + "CRL signature/binding verification failed: {0} (RFC 5280 §6.3.3; RFC 6487 §5; RFC 9829 §3.1)" + )] + CrlVerify(#[from] CrlVerifyError), + + #[error( + "CRL not valid at validation_time (RFC 5280 §6.3.3(g); RFC 5280 §5.1.2.4-§5.1.2.5; RFC 6487 §5)" + )] + CrlNotValidAtTime, + + #[error("EE certificate is revoked by issuer CRL (RFC 5280 §6.3.3; RFC 6487 §5)")] + EeRevoked, +} + +pub fn validate_ee_cert_path( + ee_cert_der: &[u8], + issuer_ca_der: &[u8], + issuer_crl_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, + issuer_crl_rsync_uri: Option<&str>, + validation_time: time::OffsetDateTime, +) -> Result { + let ee = ResourceCertificate::decode_der(ee_cert_der)?; + if ee.kind != ResourceCertKind::Ee { + return Err(CertPathError::EeNotEe); + } + + let issuer_ca = + ResourceCertificate::decode_der(issuer_ca_der).map_err(CertPathError::IssuerDecode)?; + if issuer_ca.kind != ResourceCertKind::Ca { + return Err(CertPathError::IssuerNotCa); + } + issuer_ca + .validate_rfc6487_profile(ResourceCertificateRole::Ca) + .map_err(CertPathError::IssuerProfile)?; + let issuer_spki = parse_subject_pki_from_der(&issuer_ca.tbs.subject_public_key_info)?; + + if !x509_names_equivalent(&ee.tbs.issuer_name, &issuer_ca.tbs.subject_name) { + return Err(CertPathError::IssuerSubjectMismatch { + ee_issuer_dn: ee.tbs.issuer_name.to_string(), + issuer_subject_dn: issuer_ca.tbs.subject_name.to_string(), + }); + } + + validate_ee_aki_matches_issuer_ski(&ee, &issuer_ca)?; + if let Some(expected_issuer_uri) = issuer_ca_rsync_uri { + validate_ee_aia_points_to_issuer_uri(&ee, expected_issuer_uri)?; + } + if let Some(expected_crl_uri) = issuer_crl_rsync_uri { + validate_ee_crldp_contains_issuer_crl_uri(&ee, expected_crl_uri)?; + } + + if !time_within_validity( + validation_time, + ee.tbs.validity_not_before, + ee.tbs.validity_not_after, + ) || !time_within_validity( + validation_time, + issuer_ca.tbs.validity_not_before, + issuer_ca.tbs.validity_not_after, + ) { + return Err(CertPathError::CertificateNotValidAtTime); + } + + let ee_x509 = parse_x509_cert(ee_cert_der)?; + verify_ee_cert_signature(&ee_x509, &issuer_spki)?; + validate_ee_key_usage(&ee_x509)?; + + let issuer_crl = RpkixCrl::decode_der(issuer_crl_der)?; + issuer_crl.verify_signature_with_issuer_certificate_der(issuer_ca_der)?; + if !crl_valid_at_time(&issuer_crl, validation_time) { + return Err(CertPathError::CrlNotValidAtTime); + } + + if is_serial_revoked_by_crl(&ee, &issuer_crl) { + return Err(CertPathError::EeRevoked); + } + + Ok(ValidatedEeCertPath { + ee, + issuer_ca, + issuer_crl, + }) +} + +/// Validate the EE certificate path using a *pre-decoded issuer CA* and a *pre-decoded and +/// pre-verified issuer CRL*. +/// +/// This is a performance-oriented helper for serial runs: it avoids repeating issuer CA +/// decode and issuer CRL decode+signature verification for every signed object in a publication point. +/// +/// The caller must ensure: +/// - `issuer_ca` corresponds to `issuer_ca_der` +/// - `issuer_crl` cryptographic signature/binding has already been verified against `issuer_ca_der` +pub fn validate_ee_cert_path_with_prevalidated_issuer( + ee_cert_der: &[u8], + issuer_ca: &ResourceCertificate, + issuer_spki: &SubjectPublicKeyInfo<'_>, + issuer_crl: &RpkixCrl, + issuer_crl_revoked_serials: &HashSet>, + issuer_ca_rsync_uri: Option<&str>, + issuer_crl_rsync_uri: Option<&str>, + validation_time: time::OffsetDateTime, +) -> Result { + let ee = ResourceCertificate::decode_der(ee_cert_der)?; + validate_ee_cert_path_components( + &ee, + ee_cert_der, + issuer_ca, + issuer_spki, + issuer_crl, + issuer_crl_revoked_serials, + issuer_ca_rsync_uri, + issuer_crl_rsync_uri, + validation_time, + )?; + Ok(ee) +} + +/// Validate the EE certificate path using a pre-decoded EE certificate and a pre-validated issuer. +/// +/// This avoids re-decoding the embedded EE certificate when the caller already parsed it while +/// decoding a signed object (e.g. ROA / ASPA). +pub fn validate_ee_cert_path_with_predecoded_ee( + ee: &ResourceCertificate, + ee_cert_der: &[u8], + issuer_ca: &ResourceCertificate, + issuer_spki: &SubjectPublicKeyInfo<'_>, + issuer_crl: &RpkixCrl, + issuer_crl_revoked_serials: &HashSet>, + issuer_ca_rsync_uri: Option<&str>, + issuer_crl_rsync_uri: Option<&str>, + validation_time: time::OffsetDateTime, +) -> Result<(), CertPathError> { + validate_ee_cert_path_components( + ee, + ee_cert_der, + issuer_ca, + issuer_spki, + issuer_crl, + issuer_crl_revoked_serials, + issuer_ca_rsync_uri, + issuer_crl_rsync_uri, + validation_time, + ) +} + +/// Validate a signed-object embedded EE certificate path using signed-object cached fields. +/// +/// This avoids reparsing the EE certificate as `X509Certificate` and bypasses the +/// x509-parser signature wrapper by verifying the cached TBS/signature bytes directly. +pub fn validate_signed_object_ee_cert_path_fast( + ee: &ResourceEeCertificate, + issuer_ca: &ResourceCertificate, + issuer_spki: &SubjectPublicKeyInfo<'_>, + issuer_crl: &RpkixCrl, + issuer_crl_revoked_serials: &HashSet>, + issuer_ca_rsync_uri: Option<&str>, + issuer_crl_rsync_uri: Option<&str>, + validation_time: time::OffsetDateTime, +) -> Result<(), CertPathError> { + validate_ee_cert_path_bindings_and_status( + &ee.resource_cert, + issuer_ca, + issuer_crl, + issuer_crl_revoked_serials, + issuer_ca_rsync_uri, + issuer_crl_rsync_uri, + validation_time, + )?; + validate_ee_key_usage_summary(&ee.key_usage_summary)?; + verify_ee_cert_signature_fast(&ee.tbs_certificate_der, &ee.signature_bytes, issuer_spki)?; + Ok(()) +} + +fn validate_ee_cert_path_components( + ee: &ResourceCertificate, + ee_cert_der: &[u8], + issuer_ca: &ResourceCertificate, + issuer_spki: &SubjectPublicKeyInfo<'_>, + issuer_crl: &RpkixCrl, + issuer_crl_revoked_serials: &HashSet>, + issuer_ca_rsync_uri: Option<&str>, + issuer_crl_rsync_uri: Option<&str>, + validation_time: time::OffsetDateTime, +) -> Result<(), CertPathError> { + validate_ee_cert_path_bindings_and_status( + ee, + issuer_ca, + issuer_crl, + issuer_crl_revoked_serials, + issuer_ca_rsync_uri, + issuer_crl_rsync_uri, + validation_time, + )?; + + let ee_x509 = parse_x509_cert(ee_cert_der)?; + verify_ee_cert_signature(&ee_x509, issuer_spki)?; + validate_ee_key_usage(&ee_x509)?; + + Ok(()) +} + +fn validate_ee_cert_path_bindings_and_status( + ee: &ResourceCertificate, + issuer_ca: &ResourceCertificate, + issuer_crl: &RpkixCrl, + issuer_crl_revoked_serials: &HashSet>, + issuer_ca_rsync_uri: Option<&str>, + issuer_crl_rsync_uri: Option<&str>, + validation_time: time::OffsetDateTime, +) -> Result<(), CertPathError> { + if ee.kind != ResourceCertKind::Ee { + return Err(CertPathError::EeNotEe); + } + if issuer_ca.kind != ResourceCertKind::Ca { + return Err(CertPathError::IssuerNotCa); + } + issuer_ca + .validate_rfc6487_profile(ResourceCertificateRole::Ca) + .map_err(CertPathError::IssuerProfile)?; + + if !x509_names_equivalent(&ee.tbs.issuer_name, &issuer_ca.tbs.subject_name) { + return Err(CertPathError::IssuerSubjectMismatch { + ee_issuer_dn: ee.tbs.issuer_name.to_string(), + issuer_subject_dn: issuer_ca.tbs.subject_name.to_string(), + }); + } + + validate_ee_aki_matches_issuer_ski(ee, issuer_ca)?; + if let Some(expected_issuer_uri) = issuer_ca_rsync_uri { + validate_ee_aia_points_to_issuer_uri(ee, expected_issuer_uri)?; + } + if let Some(expected_crl_uri) = issuer_crl_rsync_uri { + validate_ee_crldp_contains_issuer_crl_uri(ee, expected_crl_uri)?; + } + + if !time_within_validity( + validation_time, + ee.tbs.validity_not_before, + ee.tbs.validity_not_after, + ) || !time_within_validity( + validation_time, + issuer_ca.tbs.validity_not_before, + issuer_ca.tbs.validity_not_after, + ) { + return Err(CertPathError::CertificateNotValidAtTime); + } + + if !crl_valid_at_time(issuer_crl, validation_time) { + return Err(CertPathError::CrlNotValidAtTime); + } + + let serial = BigUnsigned::from_biguint(&ee.tbs.serial_number); + if issuer_crl_revoked_serials.contains(&serial.bytes_be) { + return Err(CertPathError::EeRevoked); + } + + Ok(()) +} + +fn parse_subject_pki_from_der(der: &[u8]) -> Result, CertPathError> { + let (rem, spki) = SubjectPublicKeyInfo::from_der(der) + .map_err(|e| CertPathError::IssuerSpkiParse(e.to_string()))?; + if !rem.is_empty() { + return Err(CertPathError::IssuerSpkiTrailingBytes(rem.len())); + } + Ok(spki) +} + +fn parse_x509_cert(der: &[u8]) -> Result, CertPathError> { + let (rem, cert) = X509Certificate::from_der(der) + .map_err(|e| CertPathError::EeSignatureInvalid(e.to_string()))?; + if !rem.is_empty() { + return Err(CertPathError::EeSignatureInvalid( + "trailing bytes after EE certificate".to_string(), + )); + } + Ok(cert) +} + +fn verify_ee_cert_signature( + ee: &X509Certificate<'_>, + issuer_spki: &SubjectPublicKeyInfo<'_>, +) -> Result<(), CertPathError> { + ee.verify_signature(Some(issuer_spki)) + .map_err(|e| CertPathError::EeSignatureInvalid(e.to_string())) +} + +fn verify_ee_cert_signature_fast( + ee_tbs_der: &[u8], + ee_signature_bytes: &[u8], + issuer_spki: &SubjectPublicKeyInfo<'_>, +) -> Result<(), CertPathError> { + let key = signature::UnparsedPublicKey::new( + &signature::RSA_PKCS1_2048_8192_SHA256, + &issuer_spki.subject_public_key.data, + ); + key.verify(ee_tbs_der, ee_signature_bytes) + .map_err(|e| CertPathError::EeSignatureInvalid(e.to_string())) +} + +fn validate_ee_aki_matches_issuer_ski( + ee: &ResourceCertificate, + issuer_ca: &ResourceCertificate, +) -> Result<(), CertPathError> { + let Some(issuer_ski) = issuer_ca.tbs.extensions.subject_key_identifier.as_deref() else { + return Err(CertPathError::IssuerSkiMissing); + }; + let Some(ee_aki) = ee.tbs.extensions.authority_key_identifier.as_deref() else { + return Err(CertPathError::EeAkiMissing); + }; + if ee_aki != issuer_ski { + return Err(CertPathError::EeAkiMismatch); + } + Ok(()) +} + +fn validate_ee_aia_points_to_issuer_uri( + ee: &ResourceCertificate, + issuer_ca_rsync_uri: &str, +) -> Result<(), CertPathError> { + let Some(uris) = ee.tbs.extensions.ca_issuers_uris.as_ref() else { + return Err(CertPathError::EeAiaMissing); + }; + if uris.iter().any(|u| u.as_str() == issuer_ca_rsync_uri) { + return Ok(()); + } + if uris.iter().any(|u| u.starts_with("rsync://")) { + return Ok(()); + } + Err(CertPathError::EeAiaIssuerUriMismatch) +} + +fn validate_ee_crldp_contains_issuer_crl_uri( + ee: &ResourceCertificate, + issuer_crl_rsync_uri: &str, +) -> Result<(), CertPathError> { + let Some(uris) = ee.tbs.extensions.crl_distribution_points_uris.as_ref() else { + return Err(CertPathError::EeCrlDpMissing); + }; + if !uris.iter().any(|u| u.as_str() == issuer_crl_rsync_uri) { + return Err(CertPathError::EeCrlDpUriMismatch); + } + Ok(()) +} + +fn validate_ee_key_usage(cert: &X509Certificate<'_>) -> Result<(), CertPathError> { + let mut ku_critical: Option = None; + for ext in cert.extensions() { + if ext.oid.as_bytes() == crate::model::oid::OID_KEY_USAGE_RAW { + ku_critical = Some(ext.critical); + break; + } + } + + let Some(critical) = ku_critical else { + return Err(CertPathError::KeyUsageMissing); + }; + if !critical { + return Err(CertPathError::KeyUsageNotCritical); + } + + let Some(ku) = cert + .key_usage() + .map_err(|e| CertPathError::EeSignatureInvalid(e.to_string()))? + else { + return Err(CertPathError::KeyUsageMissing); + }; + + let v = &ku.value; + let ok = v.digital_signature() + && !v.key_cert_sign() + && !v.crl_sign() + && !v.non_repudiation() + && !v.key_encipherment() + && !v.data_encipherment() + && !v.key_agreement() + && !v.encipher_only() + && !v.decipher_only(); + if !ok { + return Err(CertPathError::KeyUsageInvalidBits); + } + Ok(()) +} + +fn validate_ee_key_usage_summary(summary: &EeKeyUsageSummary) -> Result<(), CertPathError> { + match summary { + EeKeyUsageSummary::DigitalSignatureOnly => Ok(()), + EeKeyUsageSummary::Missing => Err(CertPathError::KeyUsageMissing), + EeKeyUsageSummary::NotCritical => Err(CertPathError::KeyUsageNotCritical), + EeKeyUsageSummary::InvalidBits => Err(CertPathError::KeyUsageInvalidBits), + EeKeyUsageSummary::ParseError(err) => Err(CertPathError::KeyUsageParse(err.clone())), + } +} + +fn time_within_validity( + t: time::OffsetDateTime, + not_before: time::OffsetDateTime, + not_after: time::OffsetDateTime, +) -> bool { + let t = t.to_offset(time::UtcOffset::UTC); + let not_before = not_before.to_offset(time::UtcOffset::UTC); + let not_after = not_after.to_offset(time::UtcOffset::UTC); + t >= not_before && t <= not_after +} + +fn crl_valid_at_time(crl: &RpkixCrl, t: time::OffsetDateTime) -> bool { + let t = t.to_offset(time::UtcOffset::UTC); + let this_update = crl.this_update.utc.to_offset(time::UtcOffset::UTC); + let next_update = crl.next_update.utc.to_offset(time::UtcOffset::UTC); + t >= this_update && t < next_update +} + +fn is_serial_revoked_by_crl(ee: &ResourceCertificate, crl: &RpkixCrl) -> bool { + let serial = BigUnsigned::from_biguint(&ee.tbs.serial_number); + crl.revoked_certs + .iter() + .any(|rc| rc.serial_number == serial) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::model::common::X509NameDer; + use crate::model::oid::OID_CP_IPADDR_ASNUMBER; + use crate::model::rc::{ + BasicConstraintsProfile, CertificatePoliciesProfile, RcExtensions, ResourceCertKind, + ResourceCertificate, RpkixTbsCertificate, + }; + use der_parser::num_bigint::BigUint; + + fn dummy_cert( + kind: ResourceCertKind, + subject_dn: &str, + issuer_dn: &str, + ski: Option>, + aki: Option>, + aia: Option>, + crldp: Option>, + ) -> ResourceCertificate { + let aia = aia.map(|v| v.into_iter().map(|s| s.to_string()).collect::>()); + let crldp = crldp.map(|v| v.into_iter().map(|s| s.to_string()).collect::>()); + ResourceCertificate { + raw_der: Vec::new(), + kind, + tbs: RpkixTbsCertificate { + version: 2, + serial_number: BigUint::from(1u8), + signature_algorithm: "1.2.840.113549.1.1.11".to_string(), + issuer_name: X509NameDer(issuer_dn.as_bytes().to_vec()), + subject_name: X509NameDer(subject_dn.as_bytes().to_vec()), + validity_not_before: time::OffsetDateTime::UNIX_EPOCH, + validity_not_after: time::OffsetDateTime::UNIX_EPOCH, + subject_public_key_info: Vec::new(), + extensions: RcExtensions { + basic_constraints_ca: kind == ResourceCertKind::Ca, + basic_constraints: (kind == ResourceCertKind::Ca).then_some( + BasicConstraintsProfile { + ca: true, + critical: true, + path_len_constraint: None, + }, + ), + subject_key_identifier: ski, + authority_key_identifier: aki, + crl_distribution_points_uris: crldp, + ca_issuers_uris: aia, + subject_info_access: None, + certificate_policies_oid: (kind == ResourceCertKind::Ca) + .then_some(OID_CP_IPADDR_ASNUMBER.to_string()), + certificate_policies: (kind == ResourceCertKind::Ca).then_some( + CertificatePoliciesProfile { + policy_oid: OID_CP_IPADDR_ASNUMBER.to_string(), + qualifier_oids: Vec::new(), + }, + ), + extension_oids: Vec::new(), + ip_resources: None, + as_resources: None, + }, + }, + } + } + + #[test] + fn ee_aki_and_aia_and_crldp_binding_checks_report_errors() { + let issuer = dummy_cert( + ResourceCertKind::Ca, + "CN=issuer", + "CN=issuer", + Some(vec![1]), + None, + None, + None, + ); + + let ee = dummy_cert( + ResourceCertKind::Ee, + "CN=ee", + "CN=issuer", + Some(vec![2]), + Some(vec![9]), + Some(vec!["rsync://example.test/issuer.cer"]), + Some(vec!["rsync://example.test/issuer.crl"]), + ); + let err = validate_ee_aki_matches_issuer_ski(&ee, &issuer).unwrap_err(); + assert!(matches!(err, CertPathError::EeAkiMismatch), "{err}"); + + let issuer_missing_ski = dummy_cert( + ResourceCertKind::Ca, + "CN=issuer", + "CN=issuer", + None, + None, + None, + None, + ); + let err = validate_ee_aki_matches_issuer_ski(&ee, &issuer_missing_ski).unwrap_err(); + assert!(matches!(err, CertPathError::IssuerSkiMissing), "{err}"); + + let ee_missing_aki = dummy_cert( + ResourceCertKind::Ee, + "CN=ee", + "CN=issuer", + Some(vec![2]), + None, + Some(vec!["rsync://example.test/issuer.cer"]), + Some(vec!["rsync://example.test/issuer.crl"]), + ); + let err = validate_ee_aki_matches_issuer_ski(&ee_missing_aki, &issuer).unwrap_err(); + assert!(matches!(err, CertPathError::EeAkiMissing), "{err}"); + + let ee_missing_aia = dummy_cert( + ResourceCertKind::Ee, + "CN=ee", + "CN=issuer", + Some(vec![2]), + Some(vec![1]), + None, + Some(vec!["rsync://example.test/issuer.crl"]), + ); + let err = validate_ee_aia_points_to_issuer_uri( + &ee_missing_aia, + "rsync://example.test/issuer.cer", + ) + .unwrap_err(); + assert!(matches!(err, CertPathError::EeAiaMissing), "{err}"); + + let ee_wrong_aia = dummy_cert( + ResourceCertKind::Ee, + "CN=ee", + "CN=issuer", + Some(vec![2]), + Some(vec![1]), + Some(vec!["rsync://example.test/other.cer"]), + Some(vec!["rsync://example.test/issuer.crl"]), + ); + validate_ee_aia_points_to_issuer_uri(&ee_wrong_aia, "rsync://example.test/issuer.cer") + .expect("non-matching rsync AIA is currently accepted"); + + let ee_missing_crldp = dummy_cert( + ResourceCertKind::Ee, + "CN=ee", + "CN=issuer", + Some(vec![2]), + Some(vec![1]), + Some(vec!["rsync://example.test/issuer.cer"]), + None, + ); + let err = validate_ee_crldp_contains_issuer_crl_uri( + &ee_missing_crldp, + "rsync://example.test/issuer.crl", + ) + .unwrap_err(); + assert!(matches!(err, CertPathError::EeCrlDpMissing), "{err}"); + + let ee_wrong_crldp = dummy_cert( + ResourceCertKind::Ee, + "CN=ee", + "CN=issuer", + Some(vec![2]), + Some(vec![1]), + Some(vec!["rsync://example.test/issuer.cer"]), + Some(vec!["rsync://example.test/other.crl"]), + ); + let err = validate_ee_crldp_contains_issuer_crl_uri( + &ee_wrong_crldp, + "rsync://example.test/issuer.crl", + ) + .unwrap_err(); + assert!(matches!(err, CertPathError::EeCrlDpUriMismatch), "{err}"); + } + + #[test] + fn ee_binding_checks_accept_when_matching() { + let issuer = dummy_cert( + ResourceCertKind::Ca, + "CN=issuer", + "CN=issuer", + Some(vec![1]), + None, + None, + None, + ); + let ee = dummy_cert( + ResourceCertKind::Ee, + "CN=ee", + "CN=issuer", + Some(vec![2]), + Some(vec![1]), + Some(vec!["rsync://example.test/issuer.cer"]), + Some(vec!["rsync://example.test/issuer.crl"]), + ); + validate_ee_aki_matches_issuer_ski(&ee, &issuer).expect("aki ok"); + validate_ee_aia_points_to_issuer_uri(&ee, "rsync://example.test/issuer.cer") + .expect("aia ok"); + validate_ee_crldp_contains_issuer_crl_uri(&ee, "rsync://example.test/issuer.crl") + .expect("crldp ok"); + } +} diff --git a/src/validation/from_tal.rs b/src/validation/from_tal.rs new file mode 100644 index 0000000..56f4d66 --- /dev/null +++ b/src/validation/from_tal.rs @@ -0,0 +1,533 @@ +use url::Url; + +use crate::model::ta::{TrustAnchor, TrustAnchorError}; +use crate::model::tal::{Tal, TalDecodeError}; +use crate::repository::fetch::rsync::RsyncFetcher; +use crate::repository::sync::rrdp::Fetcher; +use crate::validation::ca_instance::{ + CaInstanceUris, CaInstanceUrisError, ca_instance_uris_from_ca_certificate, +}; +use crate::validation::run::{RunError, RunOutput, run_publication_point_once}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct DiscoveredRootCaInstance { + pub tal_url: Option, + pub trust_anchor: TrustAnchor, + pub ca_instance: CaInstanceUris, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RunFromTalOutput { + pub discovery: DiscoveredRootCaInstance, + pub run: RunOutput, +} + +#[derive(Debug, thiserror::Error)] +pub enum FromTalError { + #[error("TAL fetch failed: {0} (RFC 8630 §2.2)")] + TalFetch(String), + + #[error("TAL decode failed: {0} (RFC 8630 §2.2)")] + TalDecode(#[from] TalDecodeError), + + #[error("failed to fetch TA certificate from TAL: {0} (RFC 8630 §2.3)")] + TaFetch(String), + + #[error("failed to bind TAL and TA certificate: {0} (RFC 8630 §2.3)")] + Bind(#[from] TrustAnchorError), + + #[error("failed to discover CA instance URIs from TA certificate: {0}")] + CaInstanceUris(#[from] CaInstanceUrisError), + + #[error("run failed: {0}")] + Run(#[from] RunError), + + #[error("TAL contains no TA URIs (RFC 8630 §2.2)")] + NoTaUris, +} + +pub fn discover_root_ca_instance_from_tal_url( + http_fetcher: &dyn Fetcher, + tal_url: &str, +) -> Result { + let tal_bytes = http_fetcher + .fetch(tal_url) + .map_err(FromTalError::TalFetch)?; + let tal = Tal::decode_bytes(&tal_bytes)?; + discover_root_ca_instance_from_tal(http_fetcher, tal, Some(tal_url.to_string())) +} + +pub fn discover_root_ca_instance_from_tal_url_with_strict_name( + http_fetcher: &dyn Fetcher, + tal_url: &str, +) -> Result { + let tal_bytes = http_fetcher + .fetch(tal_url) + .map_err(FromTalError::TalFetch)?; + let tal = Tal::decode_bytes(&tal_bytes)?; + discover_root_ca_instance_from_tal_with_strict_name( + http_fetcher, + tal, + Some(tal_url.to_string()), + ) +} + +pub fn discover_root_ca_instance_from_tal_url_with_fetchers( + http_fetcher: &dyn Fetcher, + rsync_fetcher: &dyn RsyncFetcher, + tal_url: &str, +) -> Result { + let tal_bytes = http_fetcher + .fetch(tal_url) + .map_err(FromTalError::TalFetch)?; + let tal = Tal::decode_bytes(&tal_bytes)?; + discover_root_ca_instance_from_tal_with_fetchers_impl( + http_fetcher, + rsync_fetcher, + tal, + Some(tal_url.to_string()), + false, + TaUriOrder::HttpFirst, + ) +} + +pub fn discover_root_ca_instance_from_tal_url_with_fetchers_strict_name( + http_fetcher: &dyn Fetcher, + rsync_fetcher: &dyn RsyncFetcher, + tal_url: &str, +) -> Result { + let tal_bytes = http_fetcher + .fetch(tal_url) + .map_err(FromTalError::TalFetch)?; + let tal = Tal::decode_bytes(&tal_bytes)?; + discover_root_ca_instance_from_tal_with_fetchers_impl( + http_fetcher, + rsync_fetcher, + tal, + Some(tal_url.to_string()), + true, + TaUriOrder::HttpFirst, + ) +} + +pub fn discover_root_ca_instance_from_tal( + http_fetcher: &dyn Fetcher, + tal: Tal, + tal_url: Option, +) -> Result { + discover_root_ca_instance_from_tal_impl(http_fetcher, tal, tal_url, false) +} + +pub fn discover_root_ca_instance_from_tal_with_strict_name( + http_fetcher: &dyn Fetcher, + tal: Tal, + tal_url: Option, +) -> Result { + discover_root_ca_instance_from_tal_impl(http_fetcher, tal, tal_url, true) +} + +fn discover_root_ca_instance_from_tal_impl( + http_fetcher: &dyn Fetcher, + tal: Tal, + tal_url: Option, + strict_name: bool, +) -> Result { + if tal.ta_uris.is_empty() { + return Err(FromTalError::NoTaUris); + } + + let mut last_err: Option = None; + for ta_uri in tal.ta_uris.iter() { + let ta_der = match http_fetcher.fetch(ta_uri.as_str()) { + Ok(b) => b, + Err(e) => { + last_err = Some(format!("fetch {ta_uri} failed: {e}")); + continue; + } + }; + + let trust_anchor = + match bind_trust_anchor_der(tal.clone(), &ta_der, Some(ta_uri), strict_name) { + Ok(ta) => ta, + Err(e) => { + last_err = Some(format!("bind {ta_uri} failed: {e}")); + continue; + } + }; + + let ca_instance = + match ca_instance_uris_from_ca_certificate(&trust_anchor.ta_certificate.rc_ca) { + Ok(v) => v, + Err(e) => { + last_err = Some(format!("CA instance discovery failed: {e}")); + continue; + } + }; + + return Ok(DiscoveredRootCaInstance { + tal_url, + trust_anchor, + ca_instance, + }); + } + + Err(FromTalError::TaFetch(last_err.unwrap_or_else(|| { + "unknown TA candidate error".to_string() + }))) +} + +pub fn discover_root_ca_instance_from_tal_with_fetchers( + http_fetcher: &dyn Fetcher, + rsync_fetcher: &dyn RsyncFetcher, + tal: Tal, + tal_url: Option, +) -> Result { + discover_root_ca_instance_from_tal_with_fetchers_impl( + http_fetcher, + rsync_fetcher, + tal, + tal_url, + false, + TaUriOrder::RsyncFirst, + ) +} + +pub fn discover_root_ca_instance_from_tal_with_fetchers_strict_name( + http_fetcher: &dyn Fetcher, + rsync_fetcher: &dyn RsyncFetcher, + tal: Tal, + tal_url: Option, +) -> Result { + discover_root_ca_instance_from_tal_with_fetchers_impl( + http_fetcher, + rsync_fetcher, + tal, + tal_url, + true, + TaUriOrder::RsyncFirst, + ) +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum TaUriOrder { + HttpFirst, + RsyncFirst, +} + +fn discover_root_ca_instance_from_tal_with_fetchers_impl( + http_fetcher: &dyn Fetcher, + rsync_fetcher: &dyn RsyncFetcher, + tal: Tal, + tal_url: Option, + strict_name: bool, + ta_uri_order: TaUriOrder, +) -> Result { + if tal.ta_uris.is_empty() { + return Err(FromTalError::NoTaUris); + } + + let mut last_err: Option = None; + let mut ta_uris = tal.ta_uris.clone(); + ta_uris.sort_by_key(|uri| ta_uri_priority(uri, ta_uri_order)); + for ta_uri in ta_uris.iter() { + let ta_der = match fetch_ta_der(http_fetcher, rsync_fetcher, ta_uri) { + Ok(b) => b, + Err(e) => { + last_err = Some(format!("fetch {ta_uri} failed: {e}")); + continue; + } + }; + + let trust_anchor = + match bind_trust_anchor_der(tal.clone(), &ta_der, Some(ta_uri), strict_name) { + Ok(ta) => ta, + Err(e) => { + last_err = Some(format!("bind {ta_uri} failed: {e}")); + continue; + } + }; + + let ca_instance = + match ca_instance_uris_from_ca_certificate(&trust_anchor.ta_certificate.rc_ca) { + Ok(v) => v, + Err(e) => { + last_err = Some(format!("CA instance discovery failed: {e}")); + continue; + } + }; + + return Ok(DiscoveredRootCaInstance { + tal_url, + trust_anchor, + ca_instance, + }); + } + + Err(FromTalError::TaFetch(last_err.unwrap_or_else(|| { + "unknown TA candidate error".to_string() + }))) +} + +fn ta_uri_priority(uri: &Url, order: TaUriOrder) -> u8 { + match (order, uri.scheme()) { + (TaUriOrder::HttpFirst, "https" | "http") => 0, + (TaUriOrder::HttpFirst, "rsync") => 1, + (TaUriOrder::RsyncFirst, "rsync") => 0, + (TaUriOrder::RsyncFirst, "https" | "http") => 1, + _ => 2, + } +} + +fn fetch_ta_der( + http_fetcher: &dyn Fetcher, + rsync_fetcher: &dyn RsyncFetcher, + ta_uri: &Url, +) -> Result, String> { + match ta_uri.scheme() { + "https" | "http" => http_fetcher.fetch(ta_uri.as_str()), + "rsync" => fetch_ta_der_via_rsync(rsync_fetcher, ta_uri.as_str()), + scheme => Err(format!("unsupported TA URI scheme: {scheme}")), + } +} + +fn bind_trust_anchor_der( + tal: Tal, + ta_der: &[u8], + resolved_uri: Option<&Url>, + strict_name: bool, +) -> Result { + if strict_name { + TrustAnchor::bind_der_with_strict_name(tal, ta_der, resolved_uri) + } else { + TrustAnchor::bind_der(tal, ta_der, resolved_uri) + } +} + +fn fetch_ta_der_via_rsync( + rsync_fetcher: &dyn RsyncFetcher, + ta_rsync_uri: &str, +) -> Result, String> { + rsync_fetcher + .fetch_object(ta_rsync_uri) + .map_err(|e| e.to_string()) +} + +pub fn discover_root_ca_instance_from_tal_and_ta_der( + tal_bytes: &[u8], + ta_der: &[u8], + resolved_ta_uri: Option<&Url>, +) -> Result { + discover_root_ca_instance_from_tal_and_ta_der_impl(tal_bytes, ta_der, resolved_ta_uri, false) +} + +pub fn discover_root_ca_instance_from_tal_and_ta_der_with_strict_name( + tal_bytes: &[u8], + ta_der: &[u8], + resolved_ta_uri: Option<&Url>, +) -> Result { + discover_root_ca_instance_from_tal_and_ta_der_impl(tal_bytes, ta_der, resolved_ta_uri, true) +} + +fn discover_root_ca_instance_from_tal_and_ta_der_impl( + tal_bytes: &[u8], + ta_der: &[u8], + resolved_ta_uri: Option<&Url>, + strict_name: bool, +) -> Result { + let tal = Tal::decode_bytes(tal_bytes)?; + let trust_anchor = bind_trust_anchor_der(tal, ta_der, resolved_ta_uri, strict_name)?; + let ca_instance = ca_instance_uris_from_ca_certificate(&trust_anchor.ta_certificate.rc_ca)?; + Ok(DiscoveredRootCaInstance { + tal_url: None, + trust_anchor, + ca_instance, + }) +} + +pub fn canonical_tal_rsync_uri_from_bytes(tal_bytes: &[u8]) -> Result { + let tal = Tal::decode_bytes(tal_bytes)?; + tal.ta_uris + .iter() + .find(|uri| uri.scheme() == "rsync") + .cloned() + .ok_or_else(|| { + FromTalError::TaFetch("TAL contains no rsync TA URI for offline TA binding".to_string()) + }) +} + +pub fn run_root_from_tal_url_once( + store: &crate::repository::storage::RocksStore, + policy: &crate::validation::policy::Policy, + tal_url: &str, + http_fetcher: &dyn Fetcher, + rsync_fetcher: &dyn crate::repository::fetch::rsync::RsyncFetcher, + validation_time: time::OffsetDateTime, +) -> Result { + let discovery = + discover_root_ca_instance_from_tal_url_with_fetchers(http_fetcher, rsync_fetcher, tal_url)?; + + let run = run_publication_point_once( + store, + policy, + discovery.ca_instance.rrdp_notification_uri.as_deref(), + &discovery.ca_instance.rsync_base_uri, + &discovery.ca_instance.manifest_rsync_uri, + &discovery.ca_instance.publication_point_rsync_uri, + http_fetcher, + rsync_fetcher, + &discovery.trust_anchor.ta_certificate.raw_der, + None, + discovery + .trust_anchor + .ta_certificate + .rc_ca + .tbs + .extensions + .ip_resources + .as_ref(), + discovery + .trust_anchor + .ta_certificate + .rc_ca + .tbs + .extensions + .as_resources + .as_ref(), + validation_time, + )?; + + Ok(RunFromTalOutput { discovery, run }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::repository::fetch::rsync::LocalDirRsyncFetcher; + use std::collections::HashMap; + + struct MapHttpFetcher { + map: HashMap>, + } + + impl Fetcher for MapHttpFetcher { + fn fetch(&self, uri: &str) -> Result, String> { + self.map + .get(uri) + .cloned() + .ok_or_else(|| format!("no fixture mapped for {uri}")) + } + } + + struct FailingHttpFetcher; + + impl Fetcher for FailingHttpFetcher { + fn fetch(&self, uri: &str) -> Result, String> { + Err(format!("blocked test HTTP fetch: {uri}")) + } + } + + #[test] + fn discover_root_ca_instance_from_tal_with_fetchers_supports_rsync_ta_uri() { + let fixture = crate::test_support::synthetic_repository(); + let tal_bytes = std::fs::read(fixture.tal()).unwrap(); + let ta_der = std::fs::read(fixture.trust_anchor()).unwrap(); + let tal = Tal::decode_bytes(&tal_bytes).unwrap(); + let td = tempfile::tempdir().unwrap(); + let mirror_root = td.path().join("mirror"); + std::fs::create_dir_all(&mirror_root).unwrap(); + std::fs::write(mirror_root.join("ta.cer"), ta_der).unwrap(); + + let rsync = LocalDirRsyncFetcher::new(mirror_root); + let discovery = discover_root_ca_instance_from_tal_with_fetchers( + &FailingHttpFetcher, + &rsync, + tal, + None, + ) + .expect("discover via rsync TA fallback"); + assert!( + discovery + .trust_anchor + .resolved_ta_uri + .unwrap() + .as_str() + .starts_with("rsync://") + ); + } + + struct PanicRsyncFetcher; + + impl RsyncFetcher for PanicRsyncFetcher { + fn fetch_objects( + &self, + rsync_base_uri: &str, + ) -> crate::repository::fetch::rsync::RsyncFetchResult)>> { + panic!("rsync should not be used when HTTPS TA URI is available: {rsync_base_uri}") + } + } + + #[test] + fn discover_root_ca_instance_from_tal_url_with_fetchers_prefers_https_ta_uri() { + let tal_url = "https://example.test/apnic.tal"; + let fixture = crate::test_support::synthetic_repository(); + let tal_bytes = std::fs::read(fixture.tal()).unwrap(); + let ta_der = std::fs::read(fixture.trust_anchor()).unwrap(); + let https_uri = Tal::decode_bytes(&tal_bytes) + .unwrap() + .ta_uris + .into_iter() + .find(|uri| uri.scheme() == "https") + .unwrap() + .to_string(); + let mut map = HashMap::new(); + map.insert(tal_url.to_string(), tal_bytes); + map.insert(https_uri.clone(), ta_der); + let discovery = discover_root_ca_instance_from_tal_url_with_fetchers( + &MapHttpFetcher { map }, + &PanicRsyncFetcher, + tal_url, + ) + .expect("discover via HTTPS TA URI"); + assert_eq!( + discovery.trust_anchor.resolved_ta_uri.unwrap().as_str(), + https_uri + ); + } + + #[test] + fn discover_root_ca_instance_from_tal_url_with_fetchers_supports_rsync_only_tal() { + let tal_url = "https://example.test/apnic-rsync-only.tal"; + let fixture = crate::test_support::synthetic_repository(); + let tal_bytes = std::fs::read_to_string(fixture.tal()).unwrap(); + let rsync_uri = Tal::decode_bytes(tal_bytes.as_bytes()) + .unwrap() + .ta_uris + .into_iter() + .find(|uri| uri.scheme() == "rsync") + .unwrap() + .to_string(); + let key_material = tal_bytes + .split_once("\n\n") + .map(|(_, key)| key) + .expect("fixture contains TAL key material"); + let rsync_only_tal = format!("{rsync_uri}\n\n{key_material}"); + let ta_der = std::fs::read(fixture.trust_anchor()).unwrap(); + let td = tempfile::tempdir().unwrap(); + let mirror_root = td.path().join("mirror"); + std::fs::create_dir_all(&mirror_root).unwrap(); + std::fs::write(mirror_root.join("ta.cer"), ta_der).unwrap(); + + let mut map = HashMap::new(); + map.insert(tal_url.to_string(), rsync_only_tal.into_bytes()); + let discovery = discover_root_ca_instance_from_tal_url_with_fetchers( + &MapHttpFetcher { map }, + &LocalDirRsyncFetcher::new(mirror_root), + tal_url, + ) + .expect("discover via rsync-only TAL"); + assert_eq!( + discovery.trust_anchor.resolved_ta_uri.unwrap().as_str(), + rsync_uri + ); + } +} diff --git a/src/validation/manifest.rs b/src/validation/manifest.rs new file mode 100644 index 0000000..f3b575f --- /dev/null +++ b/src/validation/manifest.rs @@ -0,0 +1,19 @@ +#![allow(clippy::too_many_arguments)] + +use crate::model::manifest::{ManifestDecodeError, ManifestObject, ManifestValidateError}; +use crate::model::signed_object::SignedObjectVerifyError; +use crate::output::report::Warning; +use crate::repository::current_repo_index::CurrentRepoIndexHandle; +use crate::repository::storage::{PackFile, PackTime, RocksStore, StorageError}; +use crate::validation::cert_path::{CertPathError, validate_signed_object_ee_cert_path_fast}; +use crate::validation::policy::Policy; +use crate::validation::publication_point::PublicationPointSnapshot; +use sha2::Digest; +use std::cmp::Ordering; +use x509_parser::prelude::FromDer; + +include!("manifest/models_and_process.rs"); +include!("manifest/helpers.rs"); + +#[cfg(test)] +mod tests; diff --git a/src/validation/manifest/helpers.rs b/src/validation/manifest/helpers.rs new file mode 100644 index 0000000..6ad38fa --- /dev/null +++ b/src/validation/manifest/helpers.rs @@ -0,0 +1,96 @@ +// Manifest URI and embedded certificate validation helpers. + +fn cmp_minimal_be_unsigned(a: &[u8], b: &[u8]) -> Ordering { + // Compare two minimal big-endian byte strings as unsigned integers. + // (Leading zeros are not expected; callers store minimal big-endian.) + a.len().cmp(&b.len()).then_with(|| a.cmp(b)) +} + +fn join_rsync_dir_and_file(base: &str, file_name: &str) -> String { + if base.ends_with('/') { + format!("{base}{file_name}") + } else { + format!("{base}/{file_name}") + } +} + +fn rsync_uri_is_under_publication_point(uri: &str, publication_point_rsync_uri: &str) -> bool { + let pp = if publication_point_rsync_uri.ends_with('/') { + publication_point_rsync_uri.to_string() + } else { + format!("{publication_point_rsync_uri}/") + }; + uri.starts_with(&pp) +} + +fn validate_manifest_embedded_ee_cert_path( + manifest: &ManifestObject, + files: &[crate::repository::storage::PackFile], + issuer_ca_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, + validation_time: time::OffsetDateTime, +) -> Result<(), ManifestFreshError> { + let ee = &manifest.signed_object.signed_data.certificates[0]; + + let crl_files = files + .iter() + .filter(|f| f.rsync_uri.ends_with(".crl")) + .collect::>(); + if crl_files.is_empty() { + return Err(ManifestFreshError::NoCrlFiles); + } + + let Some(crldp_uris) = ee + .resource_cert + .tbs + .extensions + .crl_distribution_points_uris + .as_ref() + else { + return Err(ManifestFreshError::EeCrlDpMissing); + }; + + for u in crldp_uris { + let s = u.as_str(); + if let Some(f) = crl_files.iter().find(|f| f.rsync_uri == s) { + let crl_bytes = f.bytes().map_err(|e| ManifestFreshError::MissingFile { + rsync_uri: format!("{s} ({e})"), + })?; + let issuer_ca = crate::model::rc::ResourceCertificate::decode_der(issuer_ca_der) + .map_err(CertPathError::IssuerDecode)?; + let (rem, issuer_spki) = x509_parser::x509::SubjectPublicKeyInfo::from_der( + &issuer_ca.tbs.subject_public_key_info, + ) + .map_err(|e| CertPathError::IssuerSpkiParse(e.to_string()))?; + if !rem.is_empty() { + return Err(CertPathError::IssuerSpkiTrailingBytes(rem.len()).into()); + } + let issuer_crl = crate::model::crl::RpkixCrl::decode_der(crl_bytes) + .map_err(CertPathError::from)?; + let revoked_serials = issuer_crl + .revoked_certs + .iter() + .map(|rc| rc.serial_number.bytes_be.clone()) + .collect::>(); + validate_signed_object_ee_cert_path_fast( + ee, + &issuer_ca, + &issuer_spki, + &issuer_crl, + &revoked_serials, + issuer_ca_rsync_uri, + Some(f.rsync_uri.as_str()), + validation_time, + )?; + return Ok(()); + } + } + + Err(ManifestFreshError::EeCrlNotFound( + crldp_uris + .iter() + .map(|u| u.as_str()) + .collect::>() + .join(", "), + )) +} diff --git a/src/validation/manifest/models_and_process.rs b/src/validation/manifest/models_and_process.rs new file mode 100644 index 0000000..245d7d3 --- /dev/null +++ b/src/validation/manifest/models_and_process.rs @@ -0,0 +1,679 @@ +// Publication-point models and manifest processing pipeline. + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PublicationPointSource { + Fresh, + FailedFetchNoCache, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PublicationPointResult { + pub source: PublicationPointSource, + pub snapshot: PublicationPointSnapshot, + pub warnings: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct FreshValidatedPublicationPoint { + pub manifest_rsync_uri: String, + pub publication_point_rsync_uri: String, + pub manifest_number_be: Vec, + pub this_update: PackTime, + pub next_update: PackTime, + pub verified_at: PackTime, + pub manifest_bytes: Vec, + pub files: Vec, +} + +pub trait PublicationPointData { + fn manifest_rsync_uri(&self) -> &str; + fn publication_point_rsync_uri(&self) -> &str; + fn manifest_number_be(&self) -> &[u8]; + fn this_update(&self) -> &PackTime; + fn next_update(&self) -> &PackTime; + fn verified_at(&self) -> &PackTime; + fn manifest_bytes(&self) -> &[u8]; + fn files(&self) -> &[PackFile]; +} + +impl FreshValidatedPublicationPoint { + pub fn to_publication_point_snapshot(&self) -> PublicationPointSnapshot { + PublicationPointSnapshot { + format_version: PublicationPointSnapshot::FORMAT_VERSION_V1, + manifest_rsync_uri: self.manifest_rsync_uri.clone(), + publication_point_rsync_uri: self.publication_point_rsync_uri.clone(), + manifest_number_be: self.manifest_number_be.clone(), + this_update: self.this_update.clone(), + next_update: self.next_update.clone(), + verified_at: self.verified_at.clone(), + manifest_bytes: self.manifest_bytes.clone(), + files: self.files.clone(), + } + } +} + +impl PublicationPointData for FreshValidatedPublicationPoint { + fn manifest_rsync_uri(&self) -> &str { + &self.manifest_rsync_uri + } + + fn publication_point_rsync_uri(&self) -> &str { + &self.publication_point_rsync_uri + } + + fn manifest_number_be(&self) -> &[u8] { + self.manifest_number_be.as_slice() + } + + fn this_update(&self) -> &PackTime { + &self.this_update + } + + fn next_update(&self) -> &PackTime { + &self.next_update + } + + fn verified_at(&self) -> &PackTime { + &self.verified_at + } + + fn manifest_bytes(&self) -> &[u8] { + self.manifest_bytes.as_slice() + } + + fn files(&self) -> &[PackFile] { + self.files.as_slice() + } +} + +impl PublicationPointData for PublicationPointSnapshot { + fn manifest_rsync_uri(&self) -> &str { + &self.manifest_rsync_uri + } + + fn publication_point_rsync_uri(&self) -> &str { + &self.publication_point_rsync_uri + } + + fn manifest_number_be(&self) -> &[u8] { + self.manifest_number_be.as_slice() + } + + fn this_update(&self) -> &PackTime { + &self.this_update + } + + fn next_update(&self) -> &PackTime { + &self.next_update + } + + fn verified_at(&self) -> &PackTime { + &self.verified_at + } + + fn manifest_bytes(&self) -> &[u8] { + self.manifest_bytes.as_slice() + } + + fn files(&self) -> &[PackFile] { + self.files.as_slice() + } +} + +#[derive(Debug, thiserror::Error)] +pub enum ManifestFreshError { + #[error("manifest freshness state unavailable: {0}")] + Storage(#[from] StorageError), + #[error("repo sync failed: {detail} (RFC 8182 §3.4.5; RFC 9286 §6.6)")] + RepoSyncFailed { detail: String }, + + #[error( + "manifest not found in current repository view: {manifest_rsync_uri} (RFC 9286 §6.2; RFC 9286 §6.6)" + )] + MissingManifest { manifest_rsync_uri: String }, + + #[error("manifest decode failed: {0} (RFC 9286 §4; RFC 9286 §6.2; RFC 9286 §6.6)")] + Decode(#[from] ManifestDecodeError), + + #[error( + "manifest embedded EE certificate resources invalid: {0} (RFC 9286 §5.1; RFC 9286 §6.2; RFC 9286 §6.6)" + )] + EeResources(#[from] ManifestValidateError), + + #[error( + "manifest CMS signature verification failed: {0} (RFC 6488 §3; RFC 9589 §4; RFC 9286 §6.2; RFC 9286 §6.6)" + )] + Signature(#[from] SignedObjectVerifyError), + + #[error( + "manifest embedded EE certificate path validation failed: {0} (RFC 6488 §3; RFC 9286 §6.2; RFC 9286 §6.6)" + )] + EeCertPath(#[from] CertPathError), + + #[error( + "manifest embedded EE certificate CRLDistributionPoints missing (cannot validate EE certificate) (RFC 6487 §4.8.6; RFC 6488 §3; RFC 9286 §6.2; RFC 9286 §6.6)" + )] + EeCrlDpMissing, + + #[error( + "publication point contains no CRL files (cannot validate manifest EE certificate) (RFC 9286 §7; RFC 6487 §4.8.6; RFC 6488 §3; RFC 9286 §6.2; RFC 9286 §6.6)" + )] + NoCrlFiles, + + #[error( + "CRL referenced by manifest embedded EE certificate CRLDistributionPoints not found at publication point: {0} (RFC 6487 §4.8.6; RFC 9286 §4.2.1; RFC 9286 §6.2; RFC 9286 §6.6)" + )] + EeCrlNotFound(String), + + #[error( + "manifest is not valid at validation_time: this_update={this_update_rfc3339_utc} next_update={next_update_rfc3339_utc} validation_time={validation_time_rfc3339_utc} (RFC 9286 §6.3; RFC 9286 §6.6)" + )] + StaleOrEarly { + this_update_rfc3339_utc: String, + next_update_rfc3339_utc: String, + validation_time_rfc3339_utc: String, + }, + + #[error( + "manifest must reside at the same publication point as id-ad-caRepository: manifest={manifest_rsync_uri} publication_point={publication_point_rsync_uri} (RFC 9286 §6.1; RFC 9286 §6.6)" + )] + ManifestOutsidePublicationPoint { + manifest_rsync_uri: String, + publication_point_rsync_uri: String, + }, + + #[error( + "manifestNumber not higher than previously validated manifest: old={old_hex} new={new_hex} (RFC 9286 §4.2.1; RFC 9286 §6.6)" + )] + ManifestNumberNotIncreasing { old_hex: String, new_hex: String }, + + #[error( + "thisUpdate not more recent than previously validated manifest: old={old_rfc3339_utc} new={new_rfc3339_utc} (RFC 9286 §4.2.1; RFC 9286 §6.6)" + )] + ThisUpdateNotIncreasing { + old_rfc3339_utc: String, + new_rfc3339_utc: String, + }, + + #[error( + "manifest referenced file missing in current repository view: {rsync_uri} (RFC 9286 §6.4; RFC 9286 §6.6)" + )] + MissingFile { rsync_uri: String }, + + #[error("manifest file hash mismatch: {rsync_uri} (RFC 9286 §6.5; RFC 9286 §6.6)")] + HashMismatch { rsync_uri: String }, + + #[error("issuer CA certificate bytes unavailable: {detail} (RFC 6487 §4; RFC 9286 §6.2)")] + IssuerCaLoadFailed { detail: String }, +} + +#[derive(Debug, thiserror::Error)] +pub enum ManifestProcessError { + #[error("manifest processing failed: {0}")] + StopAllOutput(#[from] ManifestFreshError), + + #[error("storage error during manifest processing: {0}")] + Storage(#[from] StorageError), +} + +pub fn process_manifest_publication_point( + store: &RocksStore, + policy: &Policy, + manifest_rsync_uri: &str, + publication_point_rsync_uri: &str, + issuer_ca_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, + validation_time: time::OffsetDateTime, +) -> Result { + process_manifest_publication_point_after_repo_sync( + store, + policy, + manifest_rsync_uri, + publication_point_rsync_uri, + issuer_ca_der, + issuer_ca_rsync_uri, + validation_time, + true, + None, + ) +} + +pub fn process_manifest_publication_point_fresh_after_repo_sync( + store: &RocksStore, + manifest_rsync_uri: &str, + publication_point_rsync_uri: &str, + issuer_ca_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, + validation_time: time::OffsetDateTime, + repo_sync_ok: bool, + repo_sync_error: Option<&str>, +) -> Result { + process_manifest_publication_point_fresh_after_repo_sync_with_timing( + store, + manifest_rsync_uri, + publication_point_rsync_uri, + None, + issuer_ca_der, + issuer_ca_rsync_uri, + validation_time, + repo_sync_ok, + repo_sync_error, + ) + .map(|(fresh, _timing)| fresh) +} + +#[derive(Clone, Debug, Default)] +pub struct FreshPublicationPointTimingBreakdown { + pub current_index_lock_ms: u64, + pub manifest_load_ms: u64, + pub manifest_index_lookup_ms: u64, + pub manifest_blob_load_ms: u64, + pub manifest_decode_ms: u64, + pub anti_rollback_guard_ms: u64, + pub anti_rollback_meta_hit: bool, + pub anti_rollback_meta_miss: bool, + pub manifest_entries_ms: u64, + pub pack_files_ms: u64, + pub pack_files_index_lookup_ms: u64, + pub pack_files_blob_load_ms: u64, + pub ee_path_validate_ms: u64, + pub manifest_file_count: usize, +} + +pub fn process_manifest_publication_point_fresh_after_repo_sync_with_timing( + store: &RocksStore, + manifest_rsync_uri: &str, + publication_point_rsync_uri: &str, + current_repo_index: Option<&CurrentRepoIndexHandle>, + issuer_ca_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, + validation_time: time::OffsetDateTime, + repo_sync_ok: bool, + repo_sync_error: Option<&str>, +) -> Result< + ( + FreshValidatedPublicationPoint, + FreshPublicationPointTimingBreakdown, + ), + ManifestFreshError, +> { + if repo_sync_ok { + try_build_fresh_publication_point_with_timing( + store, + manifest_rsync_uri, + publication_point_rsync_uri, + current_repo_index, + issuer_ca_der, + issuer_ca_rsync_uri, + validation_time, + ) + } else { + Err(ManifestFreshError::RepoSyncFailed { + detail: repo_sync_error.unwrap_or("repo sync failed").to_string(), + }) + } +} + +pub fn process_manifest_publication_point_after_repo_sync( + store: &RocksStore, + policy: &Policy, + manifest_rsync_uri: &str, + publication_point_rsync_uri: &str, + issuer_ca_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, + validation_time: time::OffsetDateTime, + repo_sync_ok: bool, + repo_sync_error: Option<&str>, +) -> Result { + let fresh = if repo_sync_ok { + try_build_fresh_publication_point( + store, + manifest_rsync_uri, + publication_point_rsync_uri, + issuer_ca_der, + issuer_ca_rsync_uri, + validation_time, + ) + } else { + Err(ManifestFreshError::RepoSyncFailed { + detail: repo_sync_error.unwrap_or("repo sync failed").to_string(), + }) + }; + + match fresh { + Ok(fresh_point) => Ok(PublicationPointResult { + source: PublicationPointSource::Fresh, + snapshot: fresh_point.to_publication_point_snapshot(), + warnings: Vec::new(), + }), + Err(fresh_err) => { + let _ = policy; + Err(ManifestProcessError::StopAllOutput(fresh_err)) + } + } +} + +fn decode_and_validate_manifest_with_current_time( + manifest_bytes: &[u8], + validation_time: time::OffsetDateTime, +) -> Result { + let manifest = ManifestObject::decode_der(manifest_bytes)?; + manifest.validate_embedded_ee_cert()?; + manifest.signed_object.verify()?; + + let this_update = manifest + .manifest + .this_update + .to_offset(time::UtcOffset::UTC); + let next_update = manifest + .manifest + .next_update + .to_offset(time::UtcOffset::UTC); + let now = validation_time.to_offset(time::UtcOffset::UTC); + if now < this_update || now > next_update { + return Err(ManifestFreshError::StaleOrEarly { + this_update_rfc3339_utc: this_update + .format(&time::format_description::well_known::Rfc3339) + .expect("format thisUpdate"), + next_update_rfc3339_utc: next_update + .format(&time::format_description::well_known::Rfc3339) + .expect("format nextUpdate"), + validation_time_rfc3339_utc: now + .format(&time::format_description::well_known::Rfc3339) + .expect("format validation_time"), + }); + } + + Ok(manifest) +} + +pub(crate) fn try_build_fresh_publication_point( + store: &RocksStore, + manifest_rsync_uri: &str, + publication_point_rsync_uri: &str, + issuer_ca_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, + validation_time: time::OffsetDateTime, +) -> Result { + try_build_fresh_publication_point_with_timing( + store, + manifest_rsync_uri, + publication_point_rsync_uri, + None, + issuer_ca_der, + issuer_ca_rsync_uri, + validation_time, + ) + .map(|(fresh, _timing)| fresh) +} + +pub(crate) fn try_build_fresh_publication_point_with_timing( + store: &RocksStore, + manifest_rsync_uri: &str, + publication_point_rsync_uri: &str, + current_repo_index: Option<&CurrentRepoIndexHandle>, + issuer_ca_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, + validation_time: time::OffsetDateTime, +) -> Result< + ( + FreshValidatedPublicationPoint, + FreshPublicationPointTimingBreakdown, + ), + ManifestFreshError, +> { + let mut timing = FreshPublicationPointTimingBreakdown::default(); + let current_index_lock_started = std::time::Instant::now(); + let current_index_guard = current_repo_index.and_then(|handle| handle.read().ok()); + timing.current_index_lock_ms = current_index_lock_started.elapsed().as_millis() as u64; + + if !rsync_uri_is_under_publication_point(manifest_rsync_uri, publication_point_rsync_uri) { + return Err(ManifestFreshError::ManifestOutsidePublicationPoint { + manifest_rsync_uri: manifest_rsync_uri.to_string(), + publication_point_rsync_uri: publication_point_rsync_uri.to_string(), + }); + } + + let manifest_load_started = std::time::Instant::now(); + let manifest_bytes = if let Some(index) = current_index_guard.as_ref() { + let manifest_lookup_started = std::time::Instant::now(); + let current = index.get_by_uri(manifest_rsync_uri).ok_or_else(|| { + ManifestFreshError::MissingManifest { + manifest_rsync_uri: manifest_rsync_uri.to_string(), + } + })?; + timing.manifest_index_lookup_ms = manifest_lookup_started.elapsed().as_millis() as u64; + let manifest_blob_load_started = std::time::Instant::now(); + store + .get_blob_bytes(¤t.current_hash_hex) + .map_err(|e| ManifestFreshError::MissingManifest { + manifest_rsync_uri: format!("{manifest_rsync_uri} ({e})"), + })? + .ok_or_else(|| ManifestFreshError::MissingManifest { + manifest_rsync_uri: manifest_rsync_uri.to_string(), + }) + .inspect(|_| { + timing.manifest_blob_load_ms = + manifest_blob_load_started.elapsed().as_millis() as u64; + })? + } else { + let manifest_blob_load_started = std::time::Instant::now(); + store + .load_current_object_bytes_by_uri(manifest_rsync_uri) + .map_err(|e| ManifestFreshError::MissingManifest { + manifest_rsync_uri: format!("{manifest_rsync_uri} ({e})"), + })? + .ok_or_else(|| ManifestFreshError::MissingManifest { + manifest_rsync_uri: manifest_rsync_uri.to_string(), + }) + .inspect(|_| { + timing.manifest_blob_load_ms = + manifest_blob_load_started.elapsed().as_millis() as u64; + })? + }; + timing.manifest_load_ms = manifest_load_started.elapsed().as_millis() as u64; + + let manifest_decode_started = std::time::Instant::now(); + let manifest = + decode_and_validate_manifest_with_current_time(&manifest_bytes, validation_time)?; + timing.manifest_decode_ms = manifest_decode_started.elapsed().as_millis() as u64; + + let this_update = manifest + .manifest + .this_update + .to_offset(time::UtcOffset::UTC); + let next_update = manifest + .manifest + .next_update + .to_offset(time::UtcOffset::UTC); + let now = validation_time.to_offset(time::UtcOffset::UTC); + + // RFC 9286 §4.2.1: rollback detection for manifestNumber and thisUpdate. + // + // Important nuance for revalidation across runs: + // - If the manifestNumber is equal to the previously validated manifestNumber *and* the + // manifest bytes are identical, then this is the same manifest being revalidated and MUST + // be accepted (otherwise, RPs would incorrectly treat stable repositories as "failed fetch" + // and continue the current validation run with a clear failure if the + // repository cannot provide a valid manifest. + // - If manifestNumber is equal but the manifest bytes differ, treat this as invalid (a + // repository is not allowed to change the manifest while keeping the manifestNumber). + // - If manifestNumber is lower, treat as rollback and reject. + // - If manifestNumber is higher, require thisUpdate to be more recent than the previously + // validated thisUpdate. + let anti_rollback_started = std::time::Instant::now(); + if let Some(old_meta) = store + .get_manifest_anti_rollback_meta(manifest_rsync_uri)? + { + timing.anti_rollback_meta_hit = true; + if old_meta.manifest_rsync_uri == manifest_rsync_uri { + let new_num = manifest.manifest.manifest_number.bytes_be.as_slice(); + let old_num = old_meta.manifest_number_be.as_slice(); + match cmp_minimal_be_unsigned(new_num, old_num) { + Ordering::Greater => { + let old_this_update = old_meta + .manifest_this_update + .parse() + .expect("manifest anti-rollback metadata ensures thisUpdate parses"); + if this_update <= old_this_update { + use time::format_description::well_known::Rfc3339; + return Err(ManifestFreshError::ThisUpdateNotIncreasing { + old_rfc3339_utc: old_this_update + .to_offset(time::UtcOffset::UTC) + .format(&Rfc3339) + .expect("format old thisUpdate"), + new_rfc3339_utc: this_update + .format(&Rfc3339) + .expect("format new thisUpdate"), + }); + } + } + Ordering::Equal => { + let new_manifest_hash = sha2::Sha256::digest(&manifest_bytes); + if old_meta.manifest_sha256.as_slice() != new_manifest_hash.as_slice() { + return Err(ManifestFreshError::ManifestNumberNotIncreasing { + old_hex: hex::encode_upper(old_num), + new_hex: hex::encode_upper(new_num), + }); + } + } + Ordering::Less => { + return Err(ManifestFreshError::ManifestNumberNotIncreasing { + old_hex: hex::encode_upper(old_num), + new_hex: hex::encode_upper(new_num), + }); + } + } + } + } else { + timing.anti_rollback_meta_miss = true; + } + timing.anti_rollback_guard_ms = anti_rollback_started.elapsed().as_millis() as u64; + + let manifest_entries_started = std::time::Instant::now(); + let entries = manifest + .manifest + .parse_files() + .map_err(ManifestDecodeError::Validate)?; + timing.manifest_entries_ms = manifest_entries_started.elapsed().as_millis() as u64; + timing.manifest_file_count = entries.len(); + let mut files = Vec::with_capacity(manifest.manifest.file_count()); + let pack_files_started = std::time::Instant::now(); + let external_raw_store = store + .external_raw_store_ref() + .cloned() + .map(std::sync::Arc::new); + let external_repo_bytes = store + .external_repo_bytes_ref() + .cloned() + .map(std::sync::Arc::new); + let mut pack_files_index_lookup_duration = std::time::Duration::ZERO; + let mut pack_files_blob_load_duration = std::time::Duration::ZERO; + for entry in &entries { + let rsync_uri = + join_rsync_dir_and_file(publication_point_rsync_uri, entry.file_name.as_str()); + let current_object = if let Some(index) = current_index_guard.as_ref() { + let index_lookup_started = std::time::Instant::now(); + let current = + index + .get_by_uri(&rsync_uri) + .ok_or_else(|| ManifestFreshError::MissingFile { + rsync_uri: rsync_uri.clone(), + })?; + pack_files_index_lookup_duration += index_lookup_started.elapsed(); + crate::repository::storage::CurrentObjectWithHash { + current_hash_hex: current.current_hash_hex.clone(), + current_hash: current.current_hash, + bytes: Vec::new(), + } + } else { + let blob_load_started = std::time::Instant::now(); + store + .load_current_object_with_hash_by_uri(&rsync_uri) + .map_err(|_e| ManifestFreshError::MissingFile { + rsync_uri: rsync_uri.clone(), + })? + .ok_or_else(|| ManifestFreshError::MissingFile { + rsync_uri: rsync_uri.clone(), + }) + .inspect(|_| { + pack_files_blob_load_duration += blob_load_started.elapsed(); + })? + }; + + if current_object.current_hash != entry.hash_bytes { + return Err(ManifestFreshError::HashMismatch { rsync_uri }); + } + + if let (Some(_), Some(repo_bytes)) = + (current_index_guard.as_ref(), external_repo_bytes.as_ref()) + { + files.push(PackFile::from_lazy_repo_bytes( + rsync_uri, + current_object.current_hash_hex, + current_object.current_hash, + repo_bytes.clone(), + )); + } else if let (Some(_), Some(raw_store)) = + (current_index_guard.as_ref(), external_raw_store.as_ref()) + { + files.push(PackFile::from_lazy_external_raw_store( + rsync_uri, + current_object.current_hash_hex, + current_object.current_hash, + raw_store.clone(), + )); + } else { + let bytes = if current_object.bytes.is_empty() { + let blob_load_started = std::time::Instant::now(); + store + .get_blob_bytes(¤t_object.current_hash_hex) + .map_err(|_e| ManifestFreshError::MissingFile { + rsync_uri: rsync_uri.clone(), + })? + .ok_or_else(|| ManifestFreshError::MissingFile { + rsync_uri: rsync_uri.clone(), + }) + .inspect(|_| { + pack_files_blob_load_duration += blob_load_started.elapsed(); + })? + } else { + current_object.bytes + }; + files.push(PackFile::from_bytes_with_sha256( + rsync_uri, + bytes, + current_object.current_hash, + )); + } + } + timing.pack_files_index_lookup_ms = pack_files_index_lookup_duration.as_millis() as u64; + timing.pack_files_blob_load_ms = pack_files_blob_load_duration.as_millis() as u64; + timing.pack_files_ms = pack_files_started.elapsed().as_millis() as u64; + + // RFC 6488 §3: manifest (signed object) validity includes a valid EE cert path. + // We validate this after §6.4/§6.5 so the issuer CRL can be selected from the publication point. + let ee_path_validate_started = std::time::Instant::now(); + validate_manifest_embedded_ee_cert_path( + &manifest, + &files, + issuer_ca_der, + issuer_ca_rsync_uri, + validation_time, + )?; + timing.ee_path_validate_ms = ee_path_validate_started.elapsed().as_millis() as u64; + + Ok(( + FreshValidatedPublicationPoint { + manifest_rsync_uri: manifest_rsync_uri.to_string(), + publication_point_rsync_uri: publication_point_rsync_uri.to_string(), + manifest_number_be: manifest.manifest.manifest_number.bytes_be.clone(), + this_update: PackTime::from_utc_offset_datetime(this_update), + next_update: PackTime::from_utc_offset_datetime(next_update), + verified_at: PackTime::from_utc_offset_datetime(now), + manifest_bytes, + files, + }, + timing, + )) +} diff --git a/src/validation/manifest/tests.rs b/src/validation/manifest/tests.rs new file mode 100644 index 0000000..8b3797e --- /dev/null +++ b/src/validation/manifest/tests.rs @@ -0,0 +1,606 @@ +//! Manifest processing regression tests. +use super::*; +use crate::repository::current_repo_index::CurrentRepoIndex; +use crate::repository::storage::{RawByHashEntry, RocksStore}; +use std::path::PathBuf; + +fn fixture_repository() -> PathBuf { + crate::test_support::synthetic_repository().case_repository("baseline-v1") +} + +fn manifest_fixture_path() -> PathBuf { + fixture_repository().join("child/child.mft") +} + +fn issuer_ca_fixture_der() -> Vec { + std::fs::read(fixture_repository().join("root/child.cer")).expect("read synthetic issuer CA") +} + +fn issuer_ca_rsync_uri() -> &'static str { + "rsync://rpki-local-rsync:873/custom/root/child.cer" +} + +fn load_manifest_fixture() -> ( + ManifestObject, + Vec, + String, + String, + time::OffsetDateTime, +) { + let manifest_path = manifest_fixture_path(); + let manifest_bytes = std::fs::read(&manifest_path).expect("read synthetic manifest"); + let manifest = ManifestObject::decode_der(&manifest_bytes).expect("decode synthetic manifest"); + let manifest_rsync_uri = "rsync://rpki-local-rsync:873/custom/child/child.mft".to_string(); + let publication_point_rsync_uri = "rsync://rpki-local-rsync:873/custom/child/".to_string(); + let validation_time = manifest.manifest.this_update + time::Duration::seconds(1); + ( + manifest, + manifest_bytes, + manifest_rsync_uri, + publication_point_rsync_uri, + validation_time, + ) +} + +fn raw_by_hash_entry(uri: &str, bytes: Vec, object_type: &str) -> RawByHashEntry { + let mut entry = RawByHashEntry::from_bytes(hex::encode(sha2::Sha256::digest(&bytes)), bytes); + entry.origin_uris.push(uri.to_string()); + entry.object_type = Some(object_type.to_string()); + entry.encoding = Some("der".to_string()); + entry +} + +fn put_current_object(store: &RocksStore, rsync_uri: &str, bytes: Vec, object_type: &str) { + let hash = hex::encode(sha2::Sha256::digest(&bytes)); + store + .put_raw_by_hash_entry(&raw_by_hash_entry(rsync_uri, bytes, object_type)) + .expect("put raw_by_hash entry"); + store + .put_repository_view_entry(&crate::repository::storage::RepositoryViewEntry { + rsync_uri: rsync_uri.to_string(), + current_hash: Some(hash), + repository_source: Some("https://example.test/notification.xml".to_string()), + object_type: Some(object_type.to_string()), + state: crate::repository::storage::RepositoryViewState::Present, + }) + .expect("put repository view entry"); +} + +fn put_complete_publication_point_current_objects( + store: &RocksStore, + manifest: &ManifestObject, + manifest_rsync_uri: &str, + manifest_bytes: Vec, + publication_point_rsync_uri: &str, +) { + put_current_object(store, manifest_rsync_uri, manifest_bytes, "mft"); + for entry in manifest.manifest.parse_files().expect("parse files") { + let file_path = manifest_fixture_path() + .parent() + .unwrap() + .join(entry.file_name.as_str()); + let bytes = std::fs::read(&file_path).expect("read fixture file"); + let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name); + let object_type = rsync_uri.rsplit('.').next().unwrap_or("bin"); + put_current_object(store, &rsync_uri, bytes, object_type); + } +} + +fn put_raw_only(store: &RocksStore, rsync_uri: &str, bytes: Vec, object_type: &str) { + store + .put_raw_by_hash_entry(&raw_by_hash_entry(rsync_uri, bytes, object_type)) + .expect("put raw_by_hash entry"); +} + +fn locked_files_for_manifest( + manifest: &ManifestObject, + publication_point_rsync_uri: &str, +) -> Vec { + let manifest_path = manifest_fixture_path(); + manifest + .manifest + .parse_files() + .expect("parse files") + .into_iter() + .map(|entry| { + let file_path = manifest_path + .parent() + .unwrap() + .join(entry.file_name.as_str()); + let bytes = std::fs::read(&file_path).unwrap_or_else(|_| { + panic!("read fixture file referenced by manifest: {file_path:?}") + }); + PackFile::from_bytes_compute_sha256( + format!("{publication_point_rsync_uri}{}", entry.file_name), + bytes, + ) + }) + .collect() +} + +#[test] +fn try_build_fresh_publication_point_rejects_manifest_outside_publication_point() { + let temp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(temp.path()).expect("open rocksdb"); + let (_, _, manifest_rsync_uri, _, validation_time) = load_manifest_fixture(); + let err = try_build_fresh_publication_point( + &store, + &manifest_rsync_uri, + "rsync://example.test/other/", + &issuer_ca_fixture_der(), + Some(issuer_ca_rsync_uri()), + validation_time, + ) + .unwrap_err(); + assert!( + matches!( + err, + ManifestFreshError::ManifestOutsidePublicationPoint { .. } + ), + "{err}" + ); +} + +#[test] +fn try_build_fresh_publication_point_reports_missing_manifest_when_raw_store_is_empty() { + let temp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(temp.path()).expect("open rocksdb"); + let (_, _, manifest_rsync_uri, publication_point_rsync_uri, validation_time) = + load_manifest_fixture(); + let err = try_build_fresh_publication_point( + &store, + &manifest_rsync_uri, + &publication_point_rsync_uri, + &issuer_ca_fixture_der(), + Some(issuer_ca_rsync_uri()), + validation_time, + ) + .unwrap_err(); + assert!( + matches!(err, ManifestFreshError::MissingManifest { .. }), + "{err}" + ); +} + +#[test] +fn try_build_fresh_publication_point_reports_missing_locked_file() { + let temp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(temp.path()).expect("open rocksdb"); + let ( + manifest, + manifest_bytes, + manifest_rsync_uri, + publication_point_rsync_uri, + validation_time, + ) = load_manifest_fixture(); + put_current_object(&store, &manifest_rsync_uri, manifest_bytes, "mft"); + let first_non_crl = manifest + .manifest + .parse_files() + .expect("parse files") + .into_iter() + .find(|entry| !entry.file_name.ends_with(".crl")) + .expect("fixture non-crl entry"); + let file_path = manifest_fixture_path() + .parent() + .unwrap() + .join(first_non_crl.file_name.as_str()); + let bytes = std::fs::read(&file_path).expect("read fixture file"); + let rsync_uri = format!("{publication_point_rsync_uri}{}", first_non_crl.file_name); + let object_type = rsync_uri.rsplit('.').next().unwrap_or("bin"); + put_current_object(&store, &rsync_uri, bytes, object_type); + + let err = try_build_fresh_publication_point( + &store, + &manifest_rsync_uri, + &publication_point_rsync_uri, + &issuer_ca_fixture_der(), + Some(issuer_ca_rsync_uri()), + validation_time, + ) + .unwrap_err(); + assert!( + matches!(err, ManifestFreshError::MissingFile { .. }), + "{err}" + ); +} + +#[test] +fn try_build_fresh_publication_point_detects_hash_mismatch_via_repository_view_hash() { + let temp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(temp.path()).expect("open rocksdb"); + let ( + manifest, + manifest_bytes, + manifest_rsync_uri, + publication_point_rsync_uri, + validation_time, + ) = load_manifest_fixture(); + put_current_object(&store, &manifest_rsync_uri, manifest_bytes, "mft"); + + let entries = manifest + .manifest + .parse_files() + .expect("parse files") + .into_iter() + .collect::>(); + let first = entries + .iter() + .find(|entry| !entry.file_name.ends_with(".crl")) + .expect("synthetic manifest non-CRL entry"); + + let first_uri = format!("{publication_point_rsync_uri}{}", first.file_name); + let object_type = first_uri.rsplit('.').next().unwrap_or("bin"); + put_current_object( + &store, + &first_uri, + b"wrong object bytes".to_vec(), + object_type, + ); + + for entry in entries + .iter() + .filter(|entry| entry.file_name != first.file_name) + { + let file_path = manifest_fixture_path() + .parent() + .unwrap() + .join(entry.file_name.as_str()); + let bytes = std::fs::read(&file_path).expect("read fixture file"); + let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name); + let object_type = rsync_uri.rsplit('.').next().unwrap_or("bin"); + put_current_object(&store, &rsync_uri, bytes, object_type); + } + + let err = try_build_fresh_publication_point( + &store, + &manifest_rsync_uri, + &publication_point_rsync_uri, + &issuer_ca_fixture_der(), + Some(issuer_ca_rsync_uri()), + validation_time, + ) + .unwrap_err(); + assert!( + matches!(err, ManifestFreshError::HashMismatch { .. }), + "{err}" + ); +} + +#[test] +fn try_build_fresh_publication_point_uses_current_repo_index_without_repository_view() { + let temp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(temp.path()).expect("open rocksdb"); + let ( + manifest, + manifest_bytes, + manifest_rsync_uri, + publication_point_rsync_uri, + validation_time, + ) = load_manifest_fixture(); + + put_raw_only(&store, &manifest_rsync_uri, manifest_bytes.clone(), "mft"); + let current_index = CurrentRepoIndex::shared(); + let mut entries = vec![crate::repository::storage::RepositoryViewEntry { + rsync_uri: manifest_rsync_uri.clone(), + current_hash: Some(hex::encode(sha2::Sha256::digest(&manifest_bytes))), + repository_source: Some("https://example.test/notification.xml".to_string()), + object_type: Some("mft".to_string()), + state: crate::repository::storage::RepositoryViewState::Present, + }]; + + for entry in manifest.manifest.parse_files().expect("parse files") { + let file_path = manifest_fixture_path() + .parent() + .unwrap() + .join(entry.file_name.as_str()); + let bytes = std::fs::read(&file_path).expect("read fixture file"); + let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name); + let object_type = rsync_uri.rsplit('.').next().unwrap_or("bin").to_string(); + put_raw_only(&store, &rsync_uri, bytes.clone(), &object_type); + entries.push(crate::repository::storage::RepositoryViewEntry { + rsync_uri, + current_hash: Some(hex::encode(sha2::Sha256::digest(&bytes))), + repository_source: Some("https://example.test/notification.xml".to_string()), + object_type: Some(object_type), + state: crate::repository::storage::RepositoryViewState::Present, + }); + } + + current_index + .write() + .expect("index write lock") + .apply_repository_view_entries(&entries) + .expect("apply current index"); + + assert!( + store + .get_repository_view_entry(&manifest_rsync_uri) + .expect("get repository view") + .is_none() + ); + + let (fresh, _timing) = try_build_fresh_publication_point_with_timing( + &store, + &manifest_rsync_uri, + &publication_point_rsync_uri, + Some(¤t_index), + &issuer_ca_fixture_der(), + Some(issuer_ca_rsync_uri()), + validation_time, + ) + .expect("fresh publication point via current index"); + + assert_eq!(fresh.manifest_rsync_uri, manifest_rsync_uri); + assert_eq!(fresh.files.len(), manifest.manifest.file_count()); +} + +#[test] +fn try_build_fresh_publication_point_records_anti_rollback_meta_miss() { + let temp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(temp.path()).expect("open rocksdb"); + let ( + manifest, + manifest_bytes, + manifest_rsync_uri, + publication_point_rsync_uri, + validation_time, + ) = load_manifest_fixture(); + put_complete_publication_point_current_objects( + &store, + &manifest, + &manifest_rsync_uri, + manifest_bytes, + &publication_point_rsync_uri, + ); + + let (_fresh, timing) = try_build_fresh_publication_point_with_timing( + &store, + &manifest_rsync_uri, + &publication_point_rsync_uri, + None, + &issuer_ca_fixture_der(), + Some(issuer_ca_rsync_uri()), + validation_time, + ) + .expect("fresh publication point without freshness metadata"); + + assert!(timing.anti_rollback_meta_miss); + assert!(!timing.anti_rollback_meta_hit); +} + +#[test] +fn try_build_fresh_publication_point_uses_anti_rollback_meta_hit_for_same_manifest() { + let temp = tempfile::tempdir().expect("tempdir"); + let store = RocksStore::open(temp.path()).expect("open rocksdb"); + let ( + manifest, + manifest_bytes, + manifest_rsync_uri, + publication_point_rsync_uri, + validation_time, + ) = load_manifest_fixture(); + let previous_meta = sample_manifest_meta( + &manifest, + &manifest_rsync_uri, + &publication_point_rsync_uri, + &manifest_bytes, + validation_time, + ); + store + .put_manifest_anti_rollback_meta(&previous_meta) + .expect("persist freshness"); + put_complete_publication_point_current_objects( + &store, + &manifest, + &manifest_rsync_uri, + manifest_bytes, + &publication_point_rsync_uri, + ); + + let (_fresh, timing) = try_build_fresh_publication_point_with_timing( + &store, + &manifest_rsync_uri, + &publication_point_rsync_uri, + None, + &issuer_ca_fixture_der(), + Some(issuer_ca_rsync_uri()), + validation_time, + ) + .expect("fresh publication point with matching freshness metadata"); + + assert!(timing.anti_rollback_meta_hit); + assert!(!timing.anti_rollback_meta_miss); +} + +#[test] +fn validate_manifest_embedded_ee_cert_path_rejects_missing_crl_files() { + let (manifest, _, _, publication_point_rsync_uri, validation_time) = load_manifest_fixture(); + let files = locked_files_for_manifest(&manifest, &publication_point_rsync_uri) + .into_iter() + .filter(|f| !f.rsync_uri.ends_with(".crl")) + .collect::>(); + + let err = validate_manifest_embedded_ee_cert_path( + &manifest, + &files, + &issuer_ca_fixture_der(), + Some(issuer_ca_rsync_uri()), + validation_time, + ) + .unwrap_err(); + assert!(matches!(err, ManifestFreshError::NoCrlFiles), "{err}"); +} + +#[test] +fn validate_manifest_embedded_ee_cert_path_rejects_missing_ee_crldp() { + let (mut manifest, _, _, publication_point_rsync_uri, validation_time) = + load_manifest_fixture(); + manifest.signed_object.signed_data.certificates[0] + .resource_cert + .tbs + .extensions + .crl_distribution_points_uris = None; + let files = locked_files_for_manifest(&manifest, &publication_point_rsync_uri); + + let err = validate_manifest_embedded_ee_cert_path( + &manifest, + &files, + &issuer_ca_fixture_der(), + Some(issuer_ca_rsync_uri()), + validation_time, + ) + .unwrap_err(); + assert!(matches!(err, ManifestFreshError::EeCrlDpMissing), "{err}"); +} + +#[test] +fn validate_manifest_embedded_ee_cert_path_rejects_unlisted_crldp_uri() { + let (manifest, _, _, publication_point_rsync_uri, validation_time) = load_manifest_fixture(); + let mut files = locked_files_for_manifest(&manifest, &publication_point_rsync_uri) + .into_iter() + .filter(|f| !f.rsync_uri.ends_with(".crl")) + .collect::>(); + files.push(PackFile::from_bytes_compute_sha256( + "rsync://example.test/repo/unrelated.crl", + b"dummy".to_vec(), + )); + + let err = validate_manifest_embedded_ee_cert_path( + &manifest, + &files, + &issuer_ca_fixture_der(), + Some(issuer_ca_rsync_uri()), + validation_time, + ) + .unwrap_err(); + assert!(matches!(err, ManifestFreshError::EeCrlNotFound(_)), "{err}"); +} + +#[test] +fn validate_manifest_embedded_ee_cert_path_rejects_expired_crl() { + let (manifest, _, _, publication_point_rsync_uri, _) = load_manifest_fixture(); + let files = locked_files_for_manifest(&manifest, &publication_point_rsync_uri); + let ee = &manifest.signed_object.signed_data.certificates[0]; + let crldp_uri = ee + .resource_cert + .tbs + .extensions + .crl_distribution_points_uris + .as_ref() + .and_then(|uris| uris.first()) + .expect("fixture manifest EE CRLDP") + .as_str() + .to_string(); + let crl_file = files + .iter() + .find(|file| file.rsync_uri == crldp_uri) + .expect("fixture CRL referenced by manifest EE"); + let crl = + crate::model::crl::RpkixCrl::decode_der(crl_file.bytes().expect("read fixture crl bytes")) + .expect("decode fixture crl"); + let validation_time = crl.next_update.utc; + + let err = validate_manifest_embedded_ee_cert_path( + &manifest, + &files, + &issuer_ca_fixture_der(), + Some(issuer_ca_rsync_uri()), + validation_time, + ) + .unwrap_err(); + + assert!( + matches!( + err, + ManifestFreshError::EeCertPath( + crate::validation::cert_path::CertPathError::CrlNotValidAtTime + ) + ), + "{err}" + ); +} + +fn sample_manifest_meta( + manifest: &ManifestObject, + uri: &str, + _base: &str, + bytes: &[u8], + now: time::OffsetDateTime, +) -> crate::repository::storage::ManifestAntiRollbackMeta { + crate::repository::storage::ManifestAntiRollbackMeta { + manifest_rsync_uri: uri.into(), + manifest_number_be: manifest.manifest.manifest_number.bytes_be.clone(), + manifest_this_update: PackTime::from_utc_offset_datetime(manifest.manifest.this_update), + manifest_sha256: sha2::Sha256::digest(bytes).to_vec(), + updated_at_validation_time: PackTime::from_utc_offset_datetime(now), + } +} +#[test] +fn history_survives_restart_and_rejects_rollback_without_advancing() { + for case in 0..4 { + let dir = tempfile::tempdir().unwrap(); + let (manifest, bytes, uri, base, now) = load_manifest_fixture(); + let mut meta = sample_manifest_meta(&manifest, &uri, &base, &bytes, now); + match case { + 0 => {} + 1 => { + meta.manifest_number_be = vec![0x7f; 20]; + } + 2 => { + meta.manifest_sha256 = vec![0x55; 32]; + } + 3 => { + meta.manifest_number_be = vec![0]; + } + _ => unreachable!(), + } + { + let store = RocksStore::open(dir.path()).unwrap(); + store.put_manifest_anti_rollback_meta(&meta).unwrap(); + put_complete_publication_point_current_objects( + &store, + &manifest, + &uri, + bytes.clone(), + &base, + ); + } + let store = RocksStore::open(dir.path()).unwrap(); + assert_eq!( + store.get_manifest_anti_rollback_meta(&uri).unwrap(), + Some(meta.clone()) + ); + let result = try_build_fresh_publication_point_with_timing( + &store, + &uri, + &base, + None, + &issuer_ca_fixture_der(), + Some(issuer_ca_rsync_uri()), + now, + ); + match case { + 0 => { + assert!(result.unwrap().1.anti_rollback_meta_hit); + } + 1 | 2 => { + assert!(matches!( + result, + Err(ManifestFreshError::ManifestNumberNotIncreasing { .. }) + )); + } + 3 => { + assert!(matches!( + result, + Err(ManifestFreshError::ThisUpdateNotIncreasing { .. }) + )); + } + _ => unreachable!(), + } + assert_eq!( + store.get_manifest_anti_rollback_meta(&uri).unwrap(), + Some(meta) + ); + } +} diff --git a/src/validation/mod.rs b/src/validation/mod.rs new file mode 100644 index 0000000..cab35a5 --- /dev/null +++ b/src/validation/mod.rs @@ -0,0 +1,14 @@ +pub mod ca_instance; +pub mod ca_path; +pub mod cert_path; +pub mod from_tal; +pub mod manifest; +pub mod objects; +pub mod policy; +pub mod publication_point; +pub mod run; +pub mod run_tree_from_tal; +pub mod tree; +pub mod tree_parallel; +pub mod tree_runner; +pub mod x509_name; diff --git a/src/validation/objects.rs b/src/validation/objects.rs new file mode 100644 index 0000000..c32e23f --- /dev/null +++ b/src/validation/objects.rs @@ -0,0 +1,36 @@ +#![allow(clippy::too_many_arguments)] + +use crate::model::aspa::{AspaDecodeError, AspaObject, AspaValidateError}; +use crate::model::manifest::ManifestObject; +use crate::model::rc::{ + AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressOrRange, IpPrefix as RcIpPrefix, + ResourceCertificate, +}; +use crate::model::roa::{IpPrefix, RoaAfi, RoaDecodeError, RoaObject, RoaValidateError}; +use crate::model::signed_object::{RpkiSignedObject, SignedObjectVerifyError}; +use crate::output::analysis::timing::TimingHandle; +use crate::output::audit::{ + AuditObjectKind, AuditObjectResult, ObjectAuditEntry, sha256_hex_from_32, +}; +use crate::output::report::{RfcRef, Warning, WarningCategory}; +use crate::repository::storage::{PackFile, PackTime}; +use crate::scheduler::config::ParallelPhase2Config; +use crate::scheduler::object_worker::{ + ObjectTaskExecutor, ObjectWorkerPool, ObjectWorkerSubmitError, +}; +use crate::validation::cert_path::{CertPathError, validate_signed_object_ee_cert_path_fast}; +use crate::validation::manifest::PublicationPointData; +use crate::validation::policy::{Policy, ResourceValidationMode, SignedObjectFailurePolicy}; +use crate::validation::publication_point::PublicationPointSnapshot; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; +use x509_parser::prelude::FromDer; +use x509_parser::x509::SubjectPublicKeyInfo; + +include!("objects/outputs.rs"); +include!("objects/serial_processing.rs"); +include!("objects/parallel_processing.rs"); +include!("objects/parallel_stage.rs"); +include!("objects/object_validation.rs"); +include!("objects/resource_validation.rs"); diff --git a/src/validation/objects/object_validation.rs b/src/validation/objects/object_validation.rs new file mode 100644 index 0000000..487abd0 --- /dev/null +++ b/src/validation/objects/object_validation.rs @@ -0,0 +1,100 @@ +fn process_roa_with_issuer( + file: &PackFile, + issuer_ca_der: &[u8], + issuer_ca: &ResourceCertificate, + issuer_spki: &SubjectPublicKeyInfo<'_>, + issuer_ca_rsync_uri: Option<&str>, + crl_states: &mut std::collections::HashMap, + issuer_resources_index: &IssuerResourcesIndex, + issuer_effective_ip: Option<&crate::model::rc::IpResourceSet>, + issuer_effective_as: Option<&crate::model::rc::AsResourceSet>, + validation_time: time::OffsetDateTime, + timing: Option<&TimingHandle>, + strict_cms_der: bool, + strict_name: bool, + resource_validation_mode: ResourceValidationMode, + ta_constraints: Option<&crate::ta_constraints::TaConstraints>, +) -> Result, ObjectValidateError> { + let roa = { + let _span = timing.as_ref().map(|t| t.span_phase("objects_roa_decode_and_validate_total")); + RoaObject::decode_der_with_strict_options(file.bytes().map_err(ObjectValidateError::BytesLoad)?, strict_cms_der, strict_name)? + }; + roa.validate_embedded_ee_cert()?; + roa.signed_object.verify()?; + let ee = &roa.signed_object.signed_data.certificates[0]; + let crl_uri = choose_crl_uri_for_certificate(ee.resource_cert.tbs.extensions.crl_distribution_points_uris.as_ref(), crl_states)?; + let verified_crl = ensure_issuer_crl_verified(crl_uri, crl_states, issuer_ca_der)?; + validate_signed_object_ee_cert_path_fast(ee, issuer_ca, issuer_spki, &verified_crl.crl, &verified_crl.revoked_serials, issuer_ca_rsync_uri, Some(crl_uri), validation_time)?; + let ee_vrs = validate_ee_resources_for_mode(&ee.resource_cert, issuer_effective_ip, issuer_effective_as, issuer_resources_index, resource_validation_mode)?; + if let Some(constraints) = ta_constraints { constraints.validate_ee_certificate(&ee.resource_cert)?; } + roa_to_vrps_with_vrs(&roa, ee_vrs.ip.as_ref()) +} + +fn process_roa_with_issuer_parallel( + file: &PackFile, + issuer_ca_der: &[u8], + issuer_ca: &ResourceCertificate, + issuer_spki: &SubjectPublicKeyInfo<'_>, + issuer_ca_rsync_uri: Option<&str>, + crl_states: &Mutex>, + issuer_resources_index: &IssuerResourcesIndex, + issuer_effective_ip: Option<&crate::model::rc::IpResourceSet>, + issuer_effective_as: Option<&crate::model::rc::AsResourceSet>, + validation_time: time::OffsetDateTime, + timing: Option<&TimingHandle>, + strict_cms_der: bool, + strict_name: bool, + resource_validation_mode: ResourceValidationMode, + ta_constraints: Option<&crate::ta_constraints::TaConstraints>, +) -> Result, ObjectValidateError> { + let roa = { + let _span = timing.as_ref().map(|t| t.span_phase("objects_roa_decode_and_validate_total")); + RoaObject::decode_der_with_strict_options(file.bytes().map_err(ObjectValidateError::BytesLoad)?, strict_cms_der, strict_name)? + }; + roa.validate_embedded_ee_cert()?; + roa.signed_object.verify()?; + let ee = &roa.signed_object.signed_data.certificates[0]; + let (crl_uri, verified_crl) = { + let mut states = crl_states.lock().expect("parallel issuer CRL state lock"); + let uri = choose_crl_uri_for_certificate(ee.resource_cert.tbs.extensions.crl_distribution_points_uris.as_ref(), &states)?.to_string(); + let value = ensure_issuer_crl_verified(&uri, &mut states, issuer_ca_der)?; + (uri, value) + }; + validate_signed_object_ee_cert_path_fast(ee, issuer_ca, issuer_spki, &verified_crl.crl, &verified_crl.revoked_serials, issuer_ca_rsync_uri, Some(crl_uri.as_str()), validation_time)?; + let ee_vrs = validate_ee_resources_for_mode(&ee.resource_cert, issuer_effective_ip, issuer_effective_as, issuer_resources_index, resource_validation_mode)?; + if let Some(constraints) = ta_constraints { constraints.validate_ee_certificate(&ee.resource_cert)?; } + roa_to_vrps_with_vrs(&roa, ee_vrs.ip.as_ref()) +} + +fn process_aspa_with_issuer( + file: &PackFile, + issuer_ca_der: &[u8], + issuer_ca: &ResourceCertificate, + issuer_spki: &SubjectPublicKeyInfo<'_>, + issuer_ca_rsync_uri: Option<&str>, + crl_states: &mut std::collections::HashMap, + issuer_resources_index: &IssuerResourcesIndex, + issuer_effective_ip: Option<&crate::model::rc::IpResourceSet>, + issuer_effective_as: Option<&crate::model::rc::AsResourceSet>, + validation_time: time::OffsetDateTime, + timing: Option<&TimingHandle>, + strict_cms_der: bool, + strict_name: bool, + resource_validation_mode: ResourceValidationMode, + ta_constraints: Option<&crate::ta_constraints::TaConstraints>, +) -> Result { + let aspa = { + let _span = timing.as_ref().map(|t| t.span_phase("objects_aspa_decode_and_validate_total")); + AspaObject::decode_der_with_strict_options(file.bytes().map_err(ObjectValidateError::BytesLoad)?, strict_cms_der, strict_name)? + }; + aspa.validate_embedded_ee_cert()?; + aspa.signed_object.verify()?; + let ee = &aspa.signed_object.signed_data.certificates[0]; + let crl_uri = choose_crl_uri_for_certificate(ee.resource_cert.tbs.extensions.crl_distribution_points_uris.as_ref(), crl_states)?; + let verified_crl = ensure_issuer_crl_verified(crl_uri, crl_states, issuer_ca_der)?; + validate_signed_object_ee_cert_path_fast(ee, issuer_ca, issuer_spki, &verified_crl.crl, &verified_crl.revoked_serials, issuer_ca_rsync_uri, Some(crl_uri), validation_time)?; + let ee_vrs = validate_ee_resources_for_mode(&ee.resource_cert, issuer_effective_ip, issuer_effective_as, issuer_resources_index, resource_validation_mode)?; + if let Some(constraints) = ta_constraints { constraints.validate_ee_certificate(&ee.resource_cert)?; } + validate_aspa_customer_in_vrs(&aspa, ee_vrs.asn.as_ref())?; + Ok(AspaAttestation { customer_as_id: aspa.aspa.customer_as_id, provider_as_ids: aspa.aspa.provider_as_ids.clone() }) +} diff --git a/src/validation/objects/outputs.rs b/src/validation/objects/outputs.rs new file mode 100644 index 0000000..50f8c56 --- /dev/null +++ b/src/validation/objects/outputs.rs @@ -0,0 +1,88 @@ +// Shared in-run validation types used by the fresh validation pipeline. + +const RFC_NONE: &[RfcRef] = &[]; +const RFC_CRLDP: &[RfcRef] = &[RfcRef("RFC 6487 §4.8.6")]; +const RFC_CRLDP_AND_LOCKED_PACK: &[RfcRef] = &[RfcRef("RFC 6487 §4.8.6"), RfcRef("RFC 9286 §4.2.1")]; + +fn ber_compatible_cms_warning(der: &[u8], rsync_uri: &str, object_kind: &str) -> Option { + let strict_error = RpkiSignedObject::strict_cms_der_error(der)?; + Some(Warning::new(format!("accepted BER-compatible CMS encoding for {object_kind}: {rsync_uri}: {strict_error}")) + .with_category(WarningCategory::BerCompatibleCmsEncoding) + .with_rfc_refs(&[RfcRef("X.690 §10"), RfcRef("RFC 6488 §2")]) + .with_context(rsync_uri)) +} +fn ber_compatible_cms_warning_for_file(file: &PackFile, object_kind: &str) -> Option { + ber_compatible_cms_warning(file.bytes().ok()?, &file.rsync_uri, object_kind) +} + +fn decode_resource_certificate_with_policy(der: &[u8], policy: &Policy) -> Result { + if policy.strict.name { ResourceCertificate::decode_der_with_strict_name(der) } else { ResourceCertificate::decode_der(der) } +} + +#[derive(Clone, Debug)] +pub(crate) struct VerifiedIssuerCrl { + pub(crate) crl: crate::model::crl::RpkixCrl, + pub(crate) revoked_serials: std::collections::HashSet>, +} +#[derive(Clone, Debug)] +pub(crate) enum IssuerCrlState { + Pending { bytes: Vec }, + Verified(Arc), +} + +#[derive(Clone, Debug, Default)] +pub(crate) struct IssuerResourcesIndex { + pub(crate) ip_v4: Option, Vec)>>, + pub(crate) ip_v6: Option, Vec)>>, + pub(crate) asnum: Option>, + pub(crate) rdi: Option>, +} + +fn extra_rfc_refs_for_crl_selection(error: &ObjectValidateError) -> &'static [RfcRef] { + match error { ObjectValidateError::MissingCrlDpUris => RFC_CRLDP, ObjectValidateError::CrlNotFound(_) => RFC_CRLDP_AND_LOCKED_PACK, _ => RFC_NONE } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Vrp { pub asn: u32, pub prefix: IpPrefix, pub max_length: u16 } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AspaAttestation { pub customer_as_id: u32, pub provider_as_ids: Vec } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RouterKeyPayload { + pub as_id: u32, + pub ski: Vec, + pub spki_der: Vec, + pub source_object_uri: String, + pub source_object_hash: String, + pub source_ee_cert_hash: String, + pub item_effective_until: PackTime, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ObjectsOutput { + pub vrps: Vec, + pub aspas: Vec, + pub router_keys: Vec, + pub warnings: Vec, + pub stats: ObjectsStats, + pub audit: Vec, +} +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct ObjectsStats { + pub roa_total: usize, + pub roa_ok: usize, + pub aspa_total: usize, + pub aspa_ok: usize, + pub publication_point_dropped: bool, +} + +#[derive(Debug)] +pub(crate) struct RoaTaskOk { pub(crate) vrps: Vec } +#[derive(Debug)] +pub(crate) struct RoaTaskResult { + pub(crate) publication_point_id: u64, + pub(crate) index: usize, + pub(crate) worker_index: usize, + pub(crate) queue_wait_ms: u64, + pub(crate) worker_ms: u64, + pub(crate) outcome: Result, +} diff --git a/src/validation/objects/parallel_processing.rs b/src/validation/objects/parallel_processing.rs new file mode 100644 index 0000000..12f9af1 --- /dev/null +++ b/src/validation/objects/parallel_processing.rs @@ -0,0 +1,331 @@ +/// Bounded parallel ROA validation. +/// +/// Workers share only immutable certificate/resource state and an in-run CRL +/// verification map. Nothing in this path is persisted or reused by a later +/// validation run. +pub fn process_publication_point_for_issuer_parallel_roa( + publication_point: &P, + policy: &Policy, + issuer_ca_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, + issuer_effective_ip: Option<&crate::model::rc::IpResourceSet>, + issuer_effective_as: Option<&crate::model::rc::AsResourceSet>, + validation_time: time::OffsetDateTime, + timing: Option<&TimingHandle>, + config: &ParallelPhase2Config, +) -> ObjectsOutput { + if config.object_workers <= 1 + || policy.signed_object_failure_policy == SignedObjectFailurePolicy::DropPublicationPoint + { + return process_publication_point_for_issuer_with_ta_constraints( + publication_point, + policy, + issuer_ca_der, + issuer_ca_rsync_uri, + issuer_effective_ip, + issuer_effective_as, + validation_time, + timing, + None, + ); + } + let Ok(pool) = ParallelRoaWorkerPool::new(config) else { + return process_publication_point_for_issuer_with_ta_constraints( + publication_point, + policy, + issuer_ca_der, + issuer_ca_rsync_uri, + issuer_effective_ip, + issuer_effective_as, + validation_time, + timing, + None, + ); + }; + process_publication_point_for_issuer_parallel_roa_with_pool( + publication_point, + policy, + issuer_ca_der, + issuer_ca_rsync_uri, + issuer_effective_ip, + issuer_effective_as, + validation_time, + timing, + &pool, + ) +} + +pub fn process_publication_point_for_issuer_parallel_roa_with_pool( + publication_point: &P, + policy: &Policy, + issuer_ca_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, + issuer_effective_ip: Option<&crate::model::rc::IpResourceSet>, + issuer_effective_as: Option<&crate::model::rc::AsResourceSet>, + validation_time: time::OffsetDateTime, + timing: Option<&TimingHandle>, + pool: &ParallelRoaWorkerPool, +) -> ObjectsOutput { + if policy.signed_object_failure_policy == SignedObjectFailurePolicy::DropPublicationPoint { + return process_publication_point_for_issuer_with_ta_constraints( + publication_point, + policy, + issuer_ca_der, + issuer_ca_rsync_uri, + issuer_effective_ip, + issuer_effective_as, + validation_time, + timing, + None, + ); + } + process_publication_point_for_issuer_parallel_roa_inner( + publication_point, + policy, + issuer_ca_der, + issuer_ca_rsync_uri, + issuer_effective_ip, + issuer_effective_as, + validation_time, + timing, + pool, + None, + ) + .unwrap_or_else(|_| { + process_publication_point_for_issuer_with_ta_constraints( + publication_point, + policy, + issuer_ca_der, + issuer_ca_rsync_uri, + issuer_effective_ip, + issuer_effective_as, + validation_time, + timing, + None, + ) + }) +} + +#[derive(Clone)] +pub(crate) struct RoaTaskShared { + locked_files: Arc<[PackFile]>, + issuer_ca_der: Arc<[u8]>, + issuer_ca: Arc, + issuer_spki_der: Arc<[u8]>, + issuer_ca_rsync_uri: Option>, + crl_states: Arc>>, + issuer_resources_index: Arc, + issuer_effective_ip: Option>, + issuer_effective_as: Option>, + resource_validation_mode: ResourceValidationMode, + ta_constraints: Option>, +} + +#[derive(Clone)] +pub(crate) struct OwnedRoaTask { + pub(crate) publication_point_id: u64, + index: usize, + shared: Arc, + validation_time: time::OffsetDateTime, + strict_cms_der: bool, + strict_name: bool, + pub(crate) submitted_at: Option, +} + +#[derive(Clone)] +struct RoaTaskExecutor; + +impl ObjectTaskExecutor for RoaTaskExecutor { + fn execute(&self, worker_index: usize, task: OwnedRoaTask) -> RoaTaskResult { + validate_owned_roa_task(worker_index, task) + } +} + +pub struct ParallelRoaWorkerPool { + pool: Mutex>, +} + +pub(crate) enum ParallelObjectsPrepare { + Complete(ObjectsOutput), + Staged(ParallelObjectsStage), +} + +pub(crate) struct ParallelObjectsStage { + pub(crate) publication_point_id: u64, + pub(crate) shared: Arc, + pub(crate) validation_time: time::OffsetDateTime, + pub(crate) strict_cms_der: bool, + pub(crate) strict_name: bool, + pub(crate) roa_task_indices: Vec, + pub(crate) ready_roa_results: Vec, + pub(crate) warnings: Vec, + pub(crate) stats: ObjectsStats, + pub(crate) audit: Vec, +} + +#[derive(Debug, thiserror::Error)] +pub(crate) enum ObjectValidateError { + #[error("object bytes load failed: {0}")] + BytesLoad(String), + #[error("ROA decode failed: {0}")] + RoaDecode(#[from] RoaDecodeError), + #[error("ROA embedded EE resource validation failed: {0}")] + RoaEeResources(#[from] RoaValidateError), + #[error("ASPA decode failed: {0}")] + AspaDecode(#[from] AspaDecodeError), + #[error("ASPA embedded EE resource validation failed: {0}")] + AspaEeResources(#[from] AspaValidateError), + #[error("CMS signature verification failed: {0}")] + Signature(#[from] SignedObjectVerifyError), + #[error("EE certificate path validation failed: {0}")] + CertPath(#[from] CertPathError), + #[error("certificate CRLDistributionPoints URIs missing (RFC 6487 §4.8.6)")] + MissingCrlDpUris, + #[error("no CRL available in publication point snapshot (RFC 9286 §7)")] + MissingCrlInPack, + #[error("CRL referenced by CRLDistributionPoints not found in publication point snapshot: {0}")] + CrlNotFound(String), + #[error("issuer effective IP resources missing")] + MissingIssuerEffectiveIp, + #[error("issuer effective AS resources missing")] + MissingIssuerEffectiveAs, + #[error("EE certificate resources are not a subset of issuer effective resources")] + EeResourcesNotSubset, + #[error("EE certificate violates locally configured TA constraints: {0}")] + TaConstraints(#[from] crate::ta_constraints::TaConstraintsViolation), +} + +impl ParallelRoaWorkerPool { + pub fn new(config: &ParallelPhase2Config) -> Result { + if config.object_workers <= 1 { + return Err("parallel ROA worker pool requires object_workers > 1".to_string()); + } + Ok(Self { + pool: Mutex::new(ObjectWorkerPool::new( + config.object_workers, + config.worker_queue_capacity, + RoaTaskExecutor, + )?), + }) + } + + pub(crate) fn try_submit_round_robin( + &self, + task: OwnedRoaTask, + ) -> Result> { + self.pool + .lock() + .expect("parallel ROA worker pool lock") + .try_submit_round_robin(task) + } + + pub(crate) fn recv_result_timeout( + &self, + timeout: Duration, + ) -> Result, String> { + self.pool + .lock() + .expect("parallel ROA worker pool lock") + .recv_result_timeout(timeout) + } +} + +fn validate_owned_roa_task(worker_index: usize, task: OwnedRoaTask) -> RoaTaskResult { + let worker_started = Instant::now(); + let queue_wait_ms = task + .submitted_at + .map(|submitted_at| worker_started.saturating_duration_since(submitted_at)) + .map(|duration| duration.as_millis() as u64) + .unwrap_or(0); + let shared = task.shared.as_ref(); + let file = shared + .locked_files + .get(task.index) + .expect("ROA task index must reference locked file"); + let outcome = match SubjectPublicKeyInfo::from_der(shared.issuer_spki_der.as_ref()) { + Ok(([], issuer_spki)) => process_roa_with_issuer_parallel( + file, + shared.issuer_ca_der.as_ref(), + shared.issuer_ca.as_ref(), + &issuer_spki, + shared.issuer_ca_rsync_uri.as_deref(), + shared.crl_states.as_ref(), + shared.issuer_resources_index.as_ref(), + shared.issuer_effective_ip.as_deref(), + shared.issuer_effective_as.as_deref(), + task.validation_time, + None, + task.strict_cms_der, + task.strict_name, + shared.resource_validation_mode, + shared.ta_constraints.as_deref(), + ) + .map(|vrps| RoaTaskOk { vrps }), + Ok((rem, _)) => Err(ObjectValidateError::CertPath( + CertPathError::IssuerSpkiTrailingBytes(rem.len()), + )), + Err(error) => Err(ObjectValidateError::CertPath( + CertPathError::IssuerSpkiParse(error.to_string()), + )), + }; + RoaTaskResult { + publication_point_id: task.publication_point_id, + index: task.index, + worker_index, + queue_wait_ms, + worker_ms: worker_started.elapsed().as_millis() as u64, + outcome, + } +} + +fn process_publication_point_for_issuer_parallel_roa_inner( + publication_point: &P, + policy: &Policy, + issuer_ca_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, + issuer_effective_ip: Option<&crate::model::rc::IpResourceSet>, + issuer_effective_as: Option<&crate::model::rc::AsResourceSet>, + validation_time: time::OffsetDateTime, + timing: Option<&TimingHandle>, + pool: &ParallelRoaWorkerPool, + ta_constraints: Option>, +) -> Result { + let stage = match prepare_publication_point_for_parallel_roa_and_ta_constraints( + 0, + publication_point, + policy, + issuer_ca_der, + issuer_ca_rsync_uri, + issuer_effective_ip, + issuer_effective_as, + validation_time, + ta_constraints, + ) { + ParallelObjectsPrepare::Complete(output) => return Ok(output), + ParallelObjectsPrepare::Staged(stage) => stage, + }; + let task_count = stage.roa_task_count(); + let mut pending = std::collections::VecDeque::with_capacity(task_count); + stage.append_roa_tasks_to(&mut pending); + while let Some(mut task) = pending.pop_front() { + task.submitted_at = Some(Instant::now()); + match pool.try_submit_round_robin(task) { + Ok(_) => {} + Err(ObjectWorkerSubmitError::QueueFull { task, .. }) => { + pending.push_front(task); + std::thread::yield_now(); + } + Err(ObjectWorkerSubmitError::Disconnected { .. }) => { + return Err("parallel ROA worker queue disconnected".to_string()); + } + } + } + let mut results = Vec::with_capacity(task_count); + while results.len() < task_count { + let Some(result) = pool.recv_result_timeout(Duration::from_secs(30))? else { + return Err("parallel ROA worker timed out".to_string()); + }; + results.push(result); + } + reduce_parallel_roa_stage(stage, results, timing) +} diff --git a/src/validation/objects/parallel_stage.rs b/src/validation/objects/parallel_stage.rs new file mode 100644 index 0000000..ca34013 --- /dev/null +++ b/src/validation/objects/parallel_stage.rs @@ -0,0 +1,377 @@ +impl ParallelObjectsStage { + pub(crate) fn append_roa_tasks_to( + &self, + pending: &mut std::collections::VecDeque, + ) { + self.extend_roa_tasks(|task| pending.push_back(task)); + } + + fn extend_roa_tasks(&self, mut push: F) + where + F: FnMut(OwnedRoaTask), + { + let shared = self.shared.clone(); + for index in &self.roa_task_indices { + push(OwnedRoaTask { + publication_point_id: self.publication_point_id, + index: *index, + shared: shared.clone(), + validation_time: self.validation_time, + strict_cms_der: self.strict_cms_der, + strict_name: self.strict_name, + submitted_at: None, + }); + } + } + + pub(crate) fn roa_task_count(&self) -> usize { + self.roa_task_indices.len() + } + + pub(crate) fn aspa_task_count(&self) -> usize { + self.stats.aspa_total + } + + pub(crate) fn locked_file_count(&self) -> usize { + self.shared.locked_files.len() + } +} + +pub(crate) fn prepare_publication_point_for_parallel_roa_and_ta_constraints< + P: PublicationPointData, +>( + publication_point_id: u64, + publication_point: &P, + policy: &Policy, + issuer_ca_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, + issuer_effective_ip: Option<&crate::model::rc::IpResourceSet>, + issuer_effective_as: Option<&crate::model::rc::AsResourceSet>, + validation_time: time::OffsetDateTime, + ta_constraints: Option>, +) -> ParallelObjectsPrepare { + let manifest_uri = publication_point.manifest_rsync_uri(); + let locked_files = publication_point.files(); + let mut stats = ObjectsStats { + roa_total: locked_files + .iter() + .filter(|f| f.rsync_uri.ends_with(".roa")) + .count(), + aspa_total: locked_files + .iter() + .filter(|f| f.rsync_uri.ends_with(".asa")) + .count(), + ..ObjectsStats::default() + }; + let mut warnings = Vec::new(); + let audit = Vec::new(); + let empty = |stats: ObjectsStats, warnings: Vec| ObjectsOutput { + vrps: Vec::new(), + aspas: Vec::new(), + router_keys: Vec::new(), + warnings, + stats, + audit: Vec::new(), + }; + if let Err(error) = ManifestObject::decode_der_with_strict_options( + publication_point.manifest_bytes(), + policy.strict.cms_der, + policy.strict.name, + ) { + stats.publication_point_dropped = true; + warnings.push( + Warning::new(format!( + "dropping publication point: manifest decode failed: {error}" + )) + .with_rfc_refs(&[RfcRef("RFC 9286 §4"), RfcRef("RFC 9286 §6.6")]) + .with_context(manifest_uri), + ); + return ParallelObjectsPrepare::Complete(empty(stats, warnings)); + } + if let Some(warning) = ber_compatible_cms_warning( + publication_point.manifest_bytes(), + manifest_uri, + "manifest", + ) { + warnings.push(warning); + } + let issuer_ca = match decode_resource_certificate_with_policy(issuer_ca_der, policy) { + Ok(value) => value, + Err(error) => { + stats.publication_point_dropped = true; + warnings.push( + Warning::new(format!( + "dropping publication point: issuer CA decode failed: {error}" + )) + .with_rfc_refs(&[RfcRef("RFC 6487 §7.2"), RfcRef("RFC 5280 §6.1")]) + .with_context(manifest_uri), + ); + return ParallelObjectsPrepare::Complete(empty(stats, warnings)); + } + }; + let issuer_spki_der = issuer_ca.tbs.subject_public_key_info.clone(); + match SubjectPublicKeyInfo::from_der(&issuer_spki_der) { + Ok(([], _)) => {} + Ok((remaining, _)) => { + stats.publication_point_dropped = true; + warnings.push( + Warning::new(format!( + "dropping publication point: issuer SPKI has {} trailing bytes", + remaining.len() + )) + .with_context(manifest_uri), + ); + return ParallelObjectsPrepare::Complete(empty(stats, warnings)); + } + Err(error) => { + stats.publication_point_dropped = true; + warnings.push( + Warning::new(format!( + "dropping publication point: issuer SPKI parse failed: {error}" + )) + .with_context(manifest_uri), + ); + return ParallelObjectsPrepare::Complete(empty(stats, warnings)); + } + } + let crl_states = locked_files + .iter() + .filter(|file| file.rsync_uri.ends_with(".crl")) + .filter_map(|file| { + file.bytes_cloned().ok().map(|bytes| { + ( + file.rsync_uri.clone(), + IssuerCrlState::Pending { bytes }, + ) + }) + }) + .collect::>(); + if crl_states.is_empty() && (stats.roa_total > 0 || stats.aspa_total > 0) { + stats.publication_point_dropped = true; + warnings.push( + Warning::new( + "dropping publication point: no CRL files in validated publication point", + ) + .with_rfc_refs(&[RfcRef("RFC 6487 §4.8.6"), RfcRef("RFC 9286 §7")]) + .with_context(manifest_uri), + ); + return ParallelObjectsPrepare::Complete(empty(stats, warnings)); + } + let roa_task_indices = locked_files + .iter() + .enumerate() + .filter_map(|(index, file)| file.rsync_uri.ends_with(".roa").then_some(index)) + .collect::>(); + ParallelObjectsPrepare::Staged(ParallelObjectsStage { + publication_point_id, + shared: Arc::new(RoaTaskShared { + locked_files: Arc::<[PackFile]>::from(locked_files.to_vec()), + issuer_ca_der: Arc::<[u8]>::from(issuer_ca_der.to_vec()), + issuer_ca: Arc::new(issuer_ca), + issuer_spki_der: Arc::<[u8]>::from(issuer_spki_der), + issuer_ca_rsync_uri: issuer_ca_rsync_uri.map(Arc::::from), + crl_states: Arc::new(Mutex::new(crl_states)), + issuer_resources_index: Arc::new(build_issuer_resources_index( + issuer_effective_ip, + issuer_effective_as, + )), + issuer_effective_ip: issuer_effective_ip.cloned().map(Arc::new), + issuer_effective_as: issuer_effective_as.cloned().map(Arc::new), + resource_validation_mode: policy.resource_validation_mode, + ta_constraints, + }), + validation_time, + strict_cms_der: policy.strict.cms_der, + strict_name: policy.strict.name, + roa_task_indices, + ready_roa_results: Vec::new(), + warnings, + stats, + audit, + }) +} + +pub(crate) fn reduce_parallel_roa_stage( + stage: ParallelObjectsStage, + mut roa_results: Vec, + timing: Option<&TimingHandle>, +) -> Result { + roa_results.extend(stage.ready_roa_results); + roa_results.sort_by_key(|result| result.index); + let mut result_iter = roa_results.into_iter().peekable(); + let shared = stage.shared.clone(); + let strict_cms_der = stage.strict_cms_der; + let strict_name = stage.strict_name; + let issuer_spki = SubjectPublicKeyInfo::from_der(shared.issuer_spki_der.as_ref()) + .map_err(|error| error.to_string())? + .1; + let mut stats = stage.stats; + let mut warnings = stage.warnings; + let mut audit = stage.audit; + let mut vrps = Vec::new(); + let mut aspas = Vec::new(); + let mut crl_states = shared + .crl_states + .lock() + .map_err(|_| "issuer CRL state lock poisoned")?; + for (index, file) in shared.locked_files.iter().enumerate() { + if file.rsync_uri.ends_with(".roa") { + let result = match result_iter.peek() { + Some(result) if result.index == index => { + result_iter.next().expect("peeked result") + } + Some(result) => { + return Err(format!( + "unexpected ROA task result index {} while reducing {}", + result.index, file.rsync_uri + )); + } + None => return Err(format!("missing ROA task result for {}", file.rsync_uri)), + }; + match result.outcome { + Ok(mut output) => { + stats.roa_ok += 1; + vrps.append(&mut output.vrps); + audit.push(ObjectAuditEntry { + rsync_uri: file.rsync_uri.clone(), + sha256_hex: sha256_hex_from_32(&file.sha256), + kind: AuditObjectKind::Roa, + result: AuditObjectResult::Ok, + detail: None, + }); + if let Some(warning) = ber_compatible_cms_warning_for_file(file, "ROA") { + warnings.push(warning); + } + } + Err(error) => { + audit.push(ObjectAuditEntry { + rsync_uri: file.rsync_uri.clone(), + sha256_hex: sha256_hex_from_32(&file.sha256), + kind: AuditObjectKind::Roa, + result: AuditObjectResult::Error, + detail: Some(error.to_string()), + }); + let mut refs = vec![RfcRef("RFC 6488 §3"), RfcRef("RFC 9582 §4-§5")]; + refs.extend_from_slice(extra_rfc_refs_for_crl_selection(&error)); + warnings.push( + Warning::new(format!( + "dropping invalid ROA: {}: {error}", + file.rsync_uri + )) + .with_rfc_refs(&refs) + .with_context(&file.rsync_uri), + ); + } + } + } else if file.rsync_uri.ends_with(".asa") { + match process_aspa_with_issuer( + file, + shared.issuer_ca_der.as_ref(), + shared.issuer_ca.as_ref(), + &issuer_spki, + shared.issuer_ca_rsync_uri.as_deref(), + &mut crl_states, + shared.issuer_resources_index.as_ref(), + shared.issuer_effective_ip.as_deref(), + shared.issuer_effective_as.as_deref(), + stage.validation_time, + timing, + strict_cms_der, + strict_name, + shared.resource_validation_mode, + shared.ta_constraints.as_deref(), + ) { + Ok(attestation) => { + stats.aspa_ok += 1; + aspas.push(attestation); + audit.push(ObjectAuditEntry { + rsync_uri: file.rsync_uri.clone(), + sha256_hex: sha256_hex_from_32(&file.sha256), + kind: AuditObjectKind::Aspa, + result: AuditObjectResult::Ok, + detail: None, + }); + if let Some(warning) = ber_compatible_cms_warning_for_file(file, "ASPA") { + warnings.push(warning); + } + } + Err(error) => { + audit.push(ObjectAuditEntry { + rsync_uri: file.rsync_uri.clone(), + sha256_hex: sha256_hex_from_32(&file.sha256), + kind: AuditObjectKind::Aspa, + result: AuditObjectResult::Error, + detail: Some(error.to_string()), + }); + warnings.push( + Warning::new(format!( + "dropping invalid ASPA: {}: {error}", + file.rsync_uri + )) + .with_context(&file.rsync_uri), + ); + } + } + } + } + if let Some(result) = result_iter.next() { + return Err(format!( + "unexpected trailing ROA task result at index {}", + result.index + )); + } + Ok(ObjectsOutput { + vrps, + aspas, + router_keys: Vec::new(), + warnings, + stats, + audit, + }) +} + +pub fn process_publication_point_snapshot_for_issuer( + pack: &PublicationPointSnapshot, + policy: &Policy, + issuer_ca_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, + issuer_effective_ip: Option<&crate::model::rc::IpResourceSet>, + issuer_effective_as: Option<&crate::model::rc::AsResourceSet>, + validation_time: time::OffsetDateTime, + timing: Option<&TimingHandle>, +) -> ObjectsOutput { + process_publication_point_for_issuer( + pack, + policy, + issuer_ca_der, + issuer_ca_rsync_uri, + issuer_effective_ip, + issuer_effective_as, + validation_time, + timing, + ) +} + +pub fn process_publication_point_snapshot_for_issuer_parallel_roa( + pack: &PublicationPointSnapshot, + policy: &Policy, + issuer_ca_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, + issuer_effective_ip: Option<&crate::model::rc::IpResourceSet>, + issuer_effective_as: Option<&crate::model::rc::AsResourceSet>, + validation_time: time::OffsetDateTime, + timing: Option<&TimingHandle>, + config: &ParallelPhase2Config, +) -> ObjectsOutput { + process_publication_point_for_issuer_parallel_roa( + pack, + policy, + issuer_ca_der, + issuer_ca_rsync_uri, + issuer_effective_ip, + issuer_effective_as, + validation_time, + timing, + config, + ) +} diff --git a/src/validation/objects/resource_validation.rs b/src/validation/objects/resource_validation.rs new file mode 100644 index 0000000..594944f --- /dev/null +++ b/src/validation/objects/resource_validation.rs @@ -0,0 +1,642 @@ +fn choose_crl_uri_for_certificate<'a>( + crldp_uris: Option<&'a Vec>, + crl_states: &std::collections::HashMap, +) -> Result<&'a str, ObjectValidateError> { + if crl_states.is_empty() { + return Err(ObjectValidateError::MissingCrlInPack); + } + + let Some(crldp_uris) = crldp_uris else { + return Err(ObjectValidateError::MissingCrlDpUris); + }; + + for u in crldp_uris { + let s = u.as_str(); + if crl_states.contains_key(s) { + return Ok(s); + } + } + Err(ObjectValidateError::CrlNotFound( + crldp_uris + .iter() + .map(|u| u.as_str()) + .collect::>() + .join(", "), + )) +} + +fn ensure_issuer_crl_verified( + crl_rsync_uri: &str, + crl_states: &mut std::collections::HashMap, + issuer_ca_der: &[u8], +) -> Result, CertPathError> { + let entry = crl_states + .get_mut(crl_rsync_uri) + .expect("CRL must exist in snapshot"); + match entry { + IssuerCrlState::Verified(v) => Ok(Arc::clone(v)), + IssuerCrlState::Pending { bytes } => { + let der = std::mem::take(bytes); + let crl = crate::model::crl::RpkixCrl::decode_der(&der) + .map_err(CertPathError::CrlDecode)?; + crl.verify_signature_with_issuer_certificate_der(issuer_ca_der) + .map_err(CertPathError::CrlVerify)?; + + let mut revoked_serials: std::collections::HashSet> = + std::collections::HashSet::with_capacity(crl.revoked_certs.len()); + for rc in &crl.revoked_certs { + revoked_serials.insert(rc.serial_number.bytes_be.clone()); + } + + *entry = IssuerCrlState::Verified(Arc::new(VerifiedIssuerCrl { + crl, + revoked_serials, + })); + match entry { + IssuerCrlState::Verified(v) => Ok(Arc::clone(v)), + _ => unreachable!(), + } + } + } +} + +fn validate_ee_resources_subset( + ee: &ResourceCertificate, + issuer_effective_ip: Option<&crate::model::rc::IpResourceSet>, + issuer_effective_as: Option<&crate::model::rc::AsResourceSet>, + issuer_resources_index: &IssuerResourcesIndex, +) -> Result<(), ObjectValidateError> { + if let Some(child_ip) = ee.tbs.extensions.ip_resources.as_ref() { + let Some(parent_ip) = issuer_effective_ip else { + return Err(ObjectValidateError::MissingIssuerEffectiveIp); + }; + if !ip_resources_is_subset_indexed(child_ip, parent_ip, issuer_resources_index) { + return Err(ObjectValidateError::EeResourcesNotSubset); + } + } + + if let Some(child_as) = ee.tbs.extensions.as_resources.as_ref() { + let Some(parent_as) = issuer_effective_as else { + return Err(ObjectValidateError::MissingIssuerEffectiveAs); + }; + if !as_resources_is_subset_indexed(child_as, parent_as, issuer_resources_index) { + return Err(ObjectValidateError::EeResourcesNotSubset); + } + } + + Ok(()) +} + +#[derive(Debug)] +struct EeVerifiedResources { + ip: Option, + asn: Option, +} + +fn validate_ee_resources_for_mode( + ee: &ResourceCertificate, + issuer_effective_ip: Option<&crate::model::rc::IpResourceSet>, + issuer_effective_as: Option<&crate::model::rc::AsResourceSet>, + issuer_resources_index: &IssuerResourcesIndex, + mode: ResourceValidationMode, +) -> Result { + match mode { + ResourceValidationMode::Rfc6487 => { + validate_ee_resources_subset( + ee, + issuer_effective_ip, + issuer_effective_as, + issuer_resources_index, + )?; + Ok(EeVerifiedResources { + ip: ee.tbs.extensions.ip_resources.clone(), + asn: ee.tbs.extensions.as_resources.clone(), + }) + } + ResourceValidationMode::ValidationUpdate03 => { + let ip = match ee.tbs.extensions.ip_resources.as_ref() { + Some(child_ip) => Some(intersect_ee_ip_resources_vrs( + child_ip, + issuer_effective_ip, + issuer_resources_index, + )?), + None => None, + }; + let asn = match ee.tbs.extensions.as_resources.as_ref() { + Some(child_as) => Some(intersect_ee_as_resources_vrs( + child_as, + issuer_effective_as, + issuer_resources_index, + )?), + None => None, + }; + Ok(EeVerifiedResources { ip, asn }) + } + } +} + +fn intersect_ee_ip_resources_vrs( + child_ip: &crate::model::rc::IpResourceSet, + issuer_effective_ip: Option<&crate::model::rc::IpResourceSet>, + issuer_resources_index: &IssuerResourcesIndex, +) -> Result { + if child_ip.has_any_inherit() { + return Err(ObjectValidateError::EeResourcesNotSubset); + } + let _ = issuer_effective_ip; + let mut families = Vec::new(); + for fam in &child_ip.families { + let parent_intervals = match fam.afi { + crate::model::rc::Afi::Ipv4 => issuer_resources_index.ip_v4.as_deref(), + crate::model::rc::Afi::Ipv6 => issuer_resources_index.ip_v6.as_deref(), + } + .unwrap_or(&[]); + let items = match &fam.choice { + IpAddressChoice::Inherit => return Err(ObjectValidateError::EeResourcesNotSubset), + IpAddressChoice::AddressesOrRanges(items) => items, + }; + let intersections = intersect_ip_items_with_parent_intervals(items, parent_intervals); + if !intersections.is_empty() { + families.push(crate::model::rc::IpAddressFamily { + afi: fam.afi, + choice: IpAddressChoice::AddressesOrRanges(ip_intervals_to_ranges( + fam.afi, + &intersections, + )), + }); + } + } + Ok(crate::model::rc::IpResourceSet { families }) +} + +fn intersect_ee_as_resources_vrs( + child_as: &crate::model::rc::AsResourceSet, + issuer_effective_as: Option<&crate::model::rc::AsResourceSet>, + issuer_resources_index: &IssuerResourcesIndex, +) -> Result { + let _ = issuer_effective_as; + if matches!(child_as.asnum, Some(AsIdentifierChoice::Inherit)) + || matches!(child_as.rdi, Some(AsIdentifierChoice::Inherit)) + { + return Err(ObjectValidateError::EeResourcesNotSubset); + } + let asnum = child_as.asnum.as_ref().map(|choice| { + let child_intervals = as_choice_to_merged_intervals(choice); + AsIdentifierChoice::AsIdsOrRanges(as_intervals_to_items(&intersect_as_intervals( + &child_intervals, + issuer_resources_index.asnum.as_deref().unwrap_or(&[]), + ))) + }); + let rdi = child_as.rdi.as_ref().map(|choice| { + let child_intervals = as_choice_to_merged_intervals(choice); + AsIdentifierChoice::AsIdsOrRanges(as_intervals_to_items(&intersect_as_intervals( + &child_intervals, + issuer_resources_index.rdi.as_deref().unwrap_or(&[]), + ))) + }); + Ok(crate::model::rc::AsResourceSet { asnum, rdi }) +} + +fn roa_to_vrps_with_vrs( + roa: &RoaObject, + ee_vrs_ip: Option<&crate::model::rc::IpResourceSet>, +) -> Result, ObjectValidateError> { + let vrps = roa_to_vrps(roa); + let Some(ee_vrs_ip) = ee_vrs_ip else { + return Err(ObjectValidateError::EeResourcesNotSubset); + }; + for vrp in &vrps { + let rc_prefix = roa_prefix_to_rc_prefix(&vrp.prefix); + if !ee_vrs_ip.contains_prefix(&rc_prefix) { + return Err(ObjectValidateError::EeResourcesNotSubset); + } + } + Ok(vrps) +} + +fn validate_aspa_customer_in_vrs( + aspa: &AspaObject, + ee_vrs_as: Option<&crate::model::rc::AsResourceSet>, +) -> Result<(), ObjectValidateError> { + let Some(ee_vrs_as) = ee_vrs_as else { + return Err(ObjectValidateError::EeResourcesNotSubset); + }; + if !as_resource_set_contains_asn(ee_vrs_as, aspa.aspa.customer_as_id) { + return Err(ObjectValidateError::EeResourcesNotSubset); + } + Ok(()) +} + +fn as_resource_set_contains_asn( + resources: &crate::model::rc::AsResourceSet, + asn: u32, +) -> bool { + let Some(choice) = resources.asnum.as_ref() else { + return false; + }; + match choice { + AsIdentifierChoice::Inherit => false, + AsIdentifierChoice::AsIdsOrRanges(items) => items.iter().any(|item| match item { + crate::model::rc::AsIdOrRange::Id(id) => *id == asn, + crate::model::rc::AsIdOrRange::Range { min, max } => *min <= asn && asn <= *max, + }), + } +} + +fn roa_prefix_to_rc_prefix(prefix: &IpPrefix) -> RcIpPrefix { + let afi = match prefix.afi { + RoaAfi::Ipv4 => crate::model::rc::Afi::Ipv4, + RoaAfi::Ipv6 => crate::model::rc::Afi::Ipv6, + }; + let mut addr = prefix.addr.to_vec(); + addr.truncate(afi.octets_len()); + RcIpPrefix { + afi, + prefix_len: prefix.prefix_len, + addr, + } +} + +fn as_resources_is_subset_indexed( + child: &AsResourceSet, + parent: &AsResourceSet, + idx: &IssuerResourcesIndex, +) -> bool { + let _ = parent; + as_choice_subset_indexed(child.asnum.as_ref(), idx.asnum.as_deref()) + && as_choice_subset_indexed(child.rdi.as_ref(), idx.rdi.as_deref()) +} + +fn as_choice_subset_indexed( + child: Option<&AsIdentifierChoice>, + parent_intervals: Option<&[(u32, u32)]>, +) -> bool { + let Some(child) = child else { + return true; + }; + let Some(parent_intervals) = parent_intervals else { + return false; + }; + + if matches!(child, AsIdentifierChoice::Inherit) { + return false; + } + + let child_intervals = as_choice_to_merged_intervals(child); + for (cmin, cmax) in &child_intervals { + if !as_interval_is_covered(parent_intervals, *cmin, *cmax) { + return false; + } + } + true +} + +fn as_choice_to_merged_intervals(choice: &AsIdentifierChoice) -> Vec<(u32, u32)> { + let mut v = Vec::new(); + match choice { + AsIdentifierChoice::Inherit => {} + AsIdentifierChoice::AsIdsOrRanges(items) => { + for item in items { + match item { + crate::model::rc::AsIdOrRange::Id(id) => v.push((*id, *id)), + crate::model::rc::AsIdOrRange::Range { min, max } => v.push((*min, *max)), + } + } + } + } + v.sort_by_key(|(a, _)| *a); + merge_as_intervals(&v) +} + +fn merge_as_intervals(v: &[(u32, u32)]) -> Vec<(u32, u32)> { + let mut out: Vec<(u32, u32)> = Vec::new(); + for (min, max) in v { + let Some(last) = out.last_mut() else { + out.push((*min, *max)); + continue; + }; + if *min <= last.1.saturating_add(1) { + last.1 = last.1.max(*max); + continue; + } + out.push((*min, *max)); + } + out +} + +fn as_interval_is_covered(parent: &[(u32, u32)], min: u32, max: u32) -> bool { + for (pmin, pmax) in parent { + if *pmin <= min && max <= *pmax { + return true; + } + if *pmin > min { + break; + } + } + false +} + +fn ip_resources_is_subset_indexed( + child: &crate::model::rc::IpResourceSet, + parent: &crate::model::rc::IpResourceSet, + idx: &IssuerResourcesIndex, +) -> bool { + let _ = parent; + + for fam in &child.families { + let parent_intervals = match fam.afi { + crate::model::rc::Afi::Ipv4 => idx.ip_v4.as_deref(), + crate::model::rc::Afi::Ipv6 => idx.ip_v6.as_deref(), + }; + let Some(parent_intervals) = parent_intervals else { + return false; + }; + let items = match &fam.choice { + IpAddressChoice::Inherit => return false, + IpAddressChoice::AddressesOrRanges(items) => items, + }; + + let mut child_intervals: Vec<(Vec, Vec)> = Vec::new(); + for item in items { + match item { + IpAddressOrRange::Prefix(p) => child_intervals.push(prefix_to_range(p)), + IpAddressOrRange::Range(r) => child_intervals.push((r.min.clone(), r.max.clone())), + } + } + if child_intervals.is_empty() { + continue; + } + child_intervals.sort_by(|(a, _), (b, _)| a.cmp(b)); + merge_ip_intervals_in_place(&mut child_intervals); + if !intervals_are_covered(parent_intervals, &child_intervals) { + return false; + } + } + true +} + +fn ip_items_to_merged_intervals( + items: &[crate::model::rc::IpAddressOrRange], +) -> Vec<(Vec, Vec)> { + let mut intervals = Vec::new(); + for item in items { + match item { + IpAddressOrRange::Prefix(p) => intervals.push(prefix_to_range(p)), + IpAddressOrRange::Range(r) => intervals.push((r.min.clone(), r.max.clone())), + } + } + intervals.sort_by(|(a, _), (b, _)| a.cmp(b)); + merge_ip_intervals_in_place(&mut intervals); + intervals +} + +fn intersect_ip_items_with_parent_intervals( + items: &[crate::model::rc::IpAddressOrRange], + parent_intervals: &[(Vec, Vec)], +) -> Vec<(Vec, Vec)> { + let child_intervals = ip_items_to_merged_intervals(items); + let mut out = Vec::new(); + let mut parent_index = 0usize; + for (child_min, child_max) in &child_intervals { + while parent_index < parent_intervals.len() + && parent_intervals[parent_index].1.as_slice() < child_min.as_slice() + { + parent_index += 1; + } + let mut scan = parent_index; + while scan < parent_intervals.len() + && parent_intervals[scan].0.as_slice() <= child_max.as_slice() + { + let (parent_min, parent_max) = &parent_intervals[scan]; + let min = if bytes_leq(child_min, parent_min) { + parent_min.clone() + } else { + child_min.clone() + }; + let max = if bytes_leq(child_max, parent_max) { + child_max.clone() + } else { + parent_max.clone() + }; + if bytes_leq(&min, &max) { + out.push((min, max)); + } + scan += 1; + } + } + merge_ip_intervals_in_place(&mut out); + out +} + +fn ip_intervals_to_ranges( + afi: crate::model::rc::Afi, + intervals: &[(Vec, Vec)], +) -> Vec { + intervals + .iter() + .map(|(min, max)| { + IpAddressOrRange::Range(crate::model::rc::IpAddressRange { + min: normalize_ip_bytes(afi, min), + max: normalize_ip_bytes(afi, max), + }) + }) + .collect() +} + +fn normalize_ip_bytes(afi: crate::model::rc::Afi, bytes: &[u8]) -> Vec { + let target_len = afi.octets_len(); + if bytes.len() == target_len { + return bytes.to_vec(); + } + let mut out = vec![0u8; target_len]; + let copy_len = bytes.len().min(target_len); + out[..copy_len].copy_from_slice(&bytes[..copy_len]); + out +} + +fn intersect_as_intervals(child: &[(u32, u32)], parent: &[(u32, u32)]) -> Vec<(u32, u32)> { + let mut out = Vec::new(); + let mut parent_index = 0usize; + for (child_min, child_max) in child { + while parent_index < parent.len() && parent[parent_index].1 < *child_min { + parent_index += 1; + } + let mut scan = parent_index; + while scan < parent.len() && parent[scan].0 <= *child_max { + let min = (*child_min).max(parent[scan].0); + let max = (*child_max).min(parent[scan].1); + if min <= max { + out.push((min, max)); + } + scan += 1; + } + } + merge_as_intervals(&out) +} + +fn as_intervals_to_items(intervals: &[(u32, u32)]) -> Vec { + intervals + .iter() + .map(|(min, max)| { + if min == max { + crate::model::rc::AsIdOrRange::Id(*min) + } else { + crate::model::rc::AsIdOrRange::Range { + min: *min, + max: *max, + } + } + }) + .collect() +} + +fn merge_ip_intervals_in_place(v: &mut Vec<(Vec, Vec)>) { + if v.is_empty() { + return; + } + let mut out: Vec<(Vec, Vec)> = Vec::with_capacity(v.len()); + for (min, max) in v.drain(..) { + let Some(last) = out.last_mut() else { + out.push((min, max)); + continue; + }; + if bytes_leq(&min, &last.1) || bytes_is_next(&min, &last.1) { + if bytes_leq(&last.1, &max) { + last.1 = max; + } + continue; + } + out.push((min, max)); + } + *v = out; +} + +fn intervals_are_covered(parent: &[(Vec, Vec)], child: &[(Vec, Vec)]) -> bool { + let mut i = 0usize; + for (cmin, cmax) in child { + while i < parent.len() && parent[i].1.as_slice() < cmin.as_slice() { + i += 1; + } + if i >= parent.len() { + return false; + } + let (pmin, pmax) = &parent[i]; + if !bytes_leq(pmin, cmin) || !bytes_leq(cmax, pmax) { + return false; + } + } + true +} + +fn prefix_to_range(prefix: &RcIpPrefix) -> (Vec, Vec) { + let mut min = prefix.addr.clone(); + let mut max = prefix.addr.clone(); + let bitlen = prefix.afi.ub(); + let plen = prefix.prefix_len.min(bitlen); + for bit in plen..bitlen { + let byte = (bit / 8) as usize; + let offset = 7 - (bit % 8); + let mask = 1u8 << offset; + min[byte] &= !mask; + max[byte] |= mask; + } + (min, max) +} + +fn bytes_leq(a: &[u8], b: &[u8]) -> bool { + a <= b +} + +fn bytes_is_next(a: &[u8], b: &[u8]) -> bool { + if a.len() != b.len() { + return false; + } + let mut carry: u16 = 1; + for i in (0..b.len()).rev() { + let sum = (b[i] as u16) + carry; + let expected = (sum & 0xFF) as u8; + carry = sum >> 8; + if a[i] != expected { + return false; + } + } + true +} + +fn build_issuer_resources_index( + issuer_effective_ip: Option<&crate::model::rc::IpResourceSet>, + issuer_effective_as: Option<&crate::model::rc::AsResourceSet>, +) -> IssuerResourcesIndex { + let mut idx = IssuerResourcesIndex::default(); + + if let Some(ip) = issuer_effective_ip { + let mut v4: Vec<(Vec, Vec)> = Vec::new(); + let mut v6: Vec<(Vec, Vec)> = Vec::new(); + for fam in &ip.families { + let ent = match fam.afi { + crate::model::rc::Afi::Ipv4 => &mut v4, + crate::model::rc::Afi::Ipv6 => &mut v6, + }; + match &fam.choice { + IpAddressChoice::Inherit => { + // Effective resources should not contain inherit; leave empty so subset fails. + } + IpAddressChoice::AddressesOrRanges(items) => { + for item in items { + match item { + IpAddressOrRange::Prefix(p) => ent.push(prefix_to_range(p)), + IpAddressOrRange::Range(r) => ent.push((r.min.clone(), r.max.clone())), + } + } + } + } + } + if !v4.is_empty() { + v4.sort_by(|(a, _), (b, _)| a.cmp(b)); + merge_ip_intervals_in_place(&mut v4); + idx.ip_v4 = Some(v4); + } + if !v6.is_empty() { + v6.sort_by(|(a, _), (b, _)| a.cmp(b)); + merge_ip_intervals_in_place(&mut v6); + idx.ip_v6 = Some(v6); + } + } + + if let Some(asr) = issuer_effective_as { + if let Some(choice) = asr.asnum.as_ref() + && !matches!(choice, AsIdentifierChoice::Inherit) { + idx.asnum = Some(as_choice_to_merged_intervals(choice)); + } + if let Some(choice) = asr.rdi.as_ref() + && !matches!(choice, AsIdentifierChoice::Inherit) { + idx.rdi = Some(as_choice_to_merged_intervals(choice)); + } + } + + idx +} + +fn roa_to_vrps(roa: &RoaObject) -> Vec { + let asn = roa.roa.as_id; + let mut out = Vec::new(); + for fam in &roa.roa.ip_addr_blocks { + for entry in &fam.addresses { + let max_length = entry.max_length.unwrap_or(entry.prefix.prefix_len); + out.push(Vrp { + asn, + prefix: entry.prefix.clone(), + max_length, + }); + } + } + out +} + +#[allow(dead_code)] +fn roa_afi_to_string(afi: RoaAfi) -> &'static str { + match afi { + RoaAfi::Ipv4 => "ipv4", + RoaAfi::Ipv6 => "ipv6", + } +} diff --git a/src/validation/objects/serial_processing.rs b/src/validation/objects/serial_processing.rs new file mode 100644 index 0000000..4902f3f --- /dev/null +++ b/src/validation/objects/serial_processing.rs @@ -0,0 +1,117 @@ +pub fn process_publication_point_for_issuer( + publication_point: &P, + policy: &Policy, + issuer_ca_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, + issuer_effective_ip: Option<&crate::model::rc::IpResourceSet>, + issuer_effective_as: Option<&crate::model::rc::AsResourceSet>, + validation_time: time::OffsetDateTime, + timing: Option<&TimingHandle>, +) -> ObjectsOutput { + process_publication_point_for_issuer_with_ta_constraints( + publication_point, policy, issuer_ca_der, issuer_ca_rsync_uri, + issuer_effective_ip, issuer_effective_as, validation_time, timing, None, + ) +} + +pub fn process_publication_point_for_issuer_with_ta_constraints( + publication_point: &P, + policy: &Policy, + issuer_ca_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, + issuer_effective_ip: Option<&crate::model::rc::IpResourceSet>, + issuer_effective_as: Option<&crate::model::rc::AsResourceSet>, + validation_time: time::OffsetDateTime, + timing: Option<&TimingHandle>, + ta_constraints: Option<&crate::ta_constraints::TaConstraints>, +) -> ObjectsOutput { + let manifest_uri = publication_point.manifest_rsync_uri(); + let files = publication_point.files(); + let mut stats = ObjectsStats { + roa_total: files.iter().filter(|f| f.rsync_uri.ends_with(".roa")).count(), + aspa_total: files.iter().filter(|f| f.rsync_uri.ends_with(".asa")).count(), + ..ObjectsStats::default() + }; + let mut warnings = Vec::new(); + let mut audit = Vec::new(); + let empty = |stats: ObjectsStats, warnings: Vec, audit: Vec| ObjectsOutput { + vrps: Vec::new(), aspas: Vec::new(), router_keys: Vec::new(), warnings, stats, audit, + }; + if let Err(error) = ManifestObject::decode_der_with_strict_options( + publication_point.manifest_bytes(), policy.strict.cms_der, policy.strict.name, + ) { + stats.publication_point_dropped = true; + warnings.push(Warning::new(format!("dropping publication point: manifest decode failed: {error}")) + .with_rfc_refs(&[RfcRef("RFC 9286 §4"), RfcRef("RFC 9286 §6.6")]).with_context(manifest_uri)); + return empty(stats, warnings, audit); + } + if let Some(warning) = ber_compatible_cms_warning(publication_point.manifest_bytes(), manifest_uri, "manifest") { warnings.push(warning); } + let issuer_ca = match decode_resource_certificate_with_policy(issuer_ca_der, policy) { + Ok(value) => value, + Err(error) => { + stats.publication_point_dropped = true; + warnings.push(Warning::new(format!("dropping publication point: issuer CA decode failed: {error}")) + .with_rfc_refs(&[RfcRef("RFC 6487 §7.2"), RfcRef("RFC 5280 §6.1")]).with_context(manifest_uri)); + return empty(stats, warnings, audit); + } + }; + let issuer_spki = match SubjectPublicKeyInfo::from_der(&issuer_ca.tbs.subject_public_key_info) { + Ok(([], spki)) => spki, + Ok((remaining, _)) => { + stats.publication_point_dropped = true; + warnings.push(Warning::new(format!("dropping publication point: issuer SPKI has {} trailing bytes", remaining.len())).with_context(manifest_uri)); + return empty(stats, warnings, audit); + } + Err(error) => { + stats.publication_point_dropped = true; + warnings.push(Warning::new(format!("dropping publication point: issuer SPKI parse failed: {error}")).with_context(manifest_uri)); + return empty(stats, warnings, audit); + } + }; + let mut crl_states: std::collections::HashMap = match files.iter().filter(|f| f.rsync_uri.ends_with(".crl")).map(|file| { + Ok((file.rsync_uri.clone(), IssuerCrlState::Pending { bytes: file.bytes_cloned().map_err(|e| format!("CRL bytes load failed: {e}"))? })) + }).collect::>() { Ok(value) => value, Err(error) => { stats.publication_point_dropped = true; warnings.push(Warning::new(error).with_context(manifest_uri)); return empty(stats, warnings, audit); } }; + let issuer_resources_index = build_issuer_resources_index(issuer_effective_ip, issuer_effective_as); + if crl_states.is_empty() && (stats.roa_total > 0 || stats.aspa_total > 0) { + stats.publication_point_dropped = true; + warnings.push(Warning::new("dropping publication point: no CRL files in validated publication point").with_rfc_refs(&[RfcRef("RFC 6487 §4.8.6"), RfcRef("RFC 9286 §7")]).with_context(manifest_uri)); + return empty(stats, warnings, audit); + } + let mut vrps = Vec::new(); + let mut aspas = Vec::new(); + for (index, file) in files.iter().enumerate() { + if file.rsync_uri.ends_with(".roa") { + let result = process_roa_with_issuer(file, issuer_ca_der, &issuer_ca, &issuer_spki, issuer_ca_rsync_uri, &mut crl_states, &issuer_resources_index, issuer_effective_ip, issuer_effective_as, validation_time, timing, policy.strict.cms_der, policy.strict.name, policy.resource_validation_mode, ta_constraints); + match result { + Ok(mut values) => { + stats.roa_ok += 1; + vrps.append(&mut values); + audit.push(ObjectAuditEntry { rsync_uri: file.rsync_uri.clone(), sha256_hex: sha256_hex_from_32(&file.sha256), kind: AuditObjectKind::Roa, result: AuditObjectResult::Ok, detail: None }); + if let Some(warning) = ber_compatible_cms_warning_for_file(file, "ROA") { warnings.push(warning); } + } + Err(error) => match policy.signed_object_failure_policy { + SignedObjectFailurePolicy::DropObject => { + audit.push(ObjectAuditEntry { rsync_uri: file.rsync_uri.clone(), sha256_hex: sha256_hex_from_32(&file.sha256), kind: AuditObjectKind::Roa, result: AuditObjectResult::Error, detail: Some(error.to_string()) }); + let mut refs = vec![RfcRef("RFC 6488 §3"), RfcRef("RFC 9582 §4-§5")]; refs.extend_from_slice(extra_rfc_refs_for_crl_selection(&error)); + warnings.push(Warning::new(format!("dropping invalid ROA: {}: {error}", file.rsync_uri)).with_rfc_refs(&refs).with_context(&file.rsync_uri)); + } + SignedObjectFailurePolicy::DropPublicationPoint => { + stats.publication_point_dropped = true; + warnings.push(Warning::new(format!("dropping publication point due to invalid ROA: {}: {error}", file.rsync_uri)).with_context(manifest_uri)); + for later in files.iter().skip(index + 1) { if later.rsync_uri.ends_with(".roa") || later.rsync_uri.ends_with(".asa") { audit.push(ObjectAuditEntry { rsync_uri: later.rsync_uri.clone(), sha256_hex: sha256_hex_from_32(&later.sha256), kind: if later.rsync_uri.ends_with(".roa") { AuditObjectKind::Roa } else { AuditObjectKind::Aspa }, result: AuditObjectResult::Skipped, detail: Some("skipped due to signed_object_failure_policy=drop_publication_point".to_string()) }); } } + return empty(stats, warnings, audit); + } + }, + } + } else if file.rsync_uri.ends_with(".asa") { + match process_aspa_with_issuer(file, issuer_ca_der, &issuer_ca, &issuer_spki, issuer_ca_rsync_uri, &mut crl_states, &issuer_resources_index, issuer_effective_ip, issuer_effective_as, validation_time, timing, policy.strict.cms_der, policy.strict.name, policy.resource_validation_mode, ta_constraints) { + Ok(attestation) => { stats.aspa_ok += 1; aspas.push(attestation); audit.push(ObjectAuditEntry { rsync_uri: file.rsync_uri.clone(), sha256_hex: sha256_hex_from_32(&file.sha256), kind: AuditObjectKind::Aspa, result: AuditObjectResult::Ok, detail: None }); if let Some(warning) = ber_compatible_cms_warning_for_file(file, "ASPA") { warnings.push(warning); } } + Err(error) => match policy.signed_object_failure_policy { + SignedObjectFailurePolicy::DropObject => { audit.push(ObjectAuditEntry { rsync_uri: file.rsync_uri.clone(), sha256_hex: sha256_hex_from_32(&file.sha256), kind: AuditObjectKind::Aspa, result: AuditObjectResult::Error, detail: Some(error.to_string()) }); warnings.push(Warning::new(format!("dropping invalid ASPA: {}: {error}", file.rsync_uri)).with_context(&file.rsync_uri)); } + SignedObjectFailurePolicy::DropPublicationPoint => { stats.publication_point_dropped = true; warnings.push(Warning::new(format!("dropping publication point due to invalid ASPA: {}: {error}", file.rsync_uri)).with_context(manifest_uri)); return empty(stats, warnings, audit); } + } + } + } + } + ObjectsOutput { vrps, aspas, router_keys: Vec::new(), warnings, stats, audit } +} diff --git a/src/validation/policy.rs b/src/validation/policy.rs new file mode 100644 index 0000000..5dba5d8 --- /dev/null +++ b/src/validation/policy.rs @@ -0,0 +1,196 @@ +use serde::{Deserialize, Serialize}; + +use crate::ta_constraints::TaConstraintsByTal; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[derive(Default)] +pub enum SyncPreference { + #[default] + RrdpThenRsync, + RsyncOnly, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[derive(Default)] +pub enum SignedObjectFailurePolicy { + #[default] + DropObject, + DropPublicationPoint, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +pub enum ResourceValidationMode { + #[serde(rename = "validation-update-03")] + #[default] + ValidationUpdate03, + #[serde(rename = "rfc6487")] + Rfc6487, +} + +impl ResourceValidationMode { + pub fn parse_cli_value(raw: &str) -> Result { + match raw.trim() { + "validation-update-03" => Ok(Self::ValidationUpdate03), + "rfc6487" => Ok(Self::Rfc6487), + value => Err(format!( + "unknown resource validation mode: {value}; supported: validation-update-03,rfc6487" + )), + } + } +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(default)] +pub struct StrictPolicy { + pub name: bool, + pub cms_der: bool, + pub signed_attrs: bool, +} + +impl StrictPolicy { + pub fn none() -> Self { + Self::default() + } + + pub fn all() -> Self { + Self { + name: true, + cms_der: true, + signed_attrs: true, + } + } + + pub fn parse_cli_spec(spec: Option<&str>) -> Result { + let Some(spec) = spec else { + return Ok(Self::all()); + }; + let spec = spec.trim(); + if spec.is_empty() || spec == "all" { + return Ok(Self::all()); + } + if spec == "none" { + return Ok(Self::none()); + } + + let mut out = Self::none(); + for raw in spec.split(',') { + let item = raw.trim(); + match item { + "name" => out.name = true, + "cms-der" | "cms_der" => out.cms_der = true, + "signed-attrs" | "signed_attrs" => out.signed_attrs = true, + "all" => out = Self::all(), + "none" => out = Self::none(), + "" => return Err("empty strict policy name".to_string()), + _ => { + return Err(format!( + "unknown strict policy: {item}; supported: name,cms-der,signed-attrs,all,none" + )); + } + } + } + Ok(out) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(default)] +#[derive(Default)] +pub struct Policy { + pub sync_preference: SyncPreference, + pub signed_object_failure_policy: SignedObjectFailurePolicy, + pub resource_validation_mode: ResourceValidationMode, + pub strict: StrictPolicy, + /// Locally configured, per-TAL EE certificate resource constraints. They + /// are intentionally CLI/runtime-only rather than policy-file input. + #[serde(skip)] + pub ta_constraints: TaConstraintsByTal, +} + +#[derive(Debug, thiserror::Error)] +pub enum PolicyParseError { + #[error("policy TOML parse error: {0}")] + Toml(String), +} + +impl Policy { + pub fn from_toml_str(s: &str) -> Result { + toml::from_str(s).map_err(|e| PolicyParseError::Toml(e.to_string())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn strict_policy_parses_cli_specs() { + assert_eq!( + StrictPolicy::parse_cli_spec(None).unwrap(), + StrictPolicy::all() + ); + assert_eq!( + StrictPolicy::parse_cli_spec(Some("name,signed-attrs")).unwrap(), + StrictPolicy { + name: true, + cms_der: false, + signed_attrs: true, + } + ); + assert_eq!( + StrictPolicy::parse_cli_spec(Some("none")).unwrap(), + StrictPolicy::none() + ); + assert!(StrictPolicy::parse_cli_spec(Some("bogus")).is_err()); + } + + #[test] + fn resource_validation_mode_parses_cli_values() { + assert_eq!( + ResourceValidationMode::parse_cli_value("validation-update-03").unwrap(), + ResourceValidationMode::ValidationUpdate03 + ); + assert_eq!( + ResourceValidationMode::parse_cli_value("rfc6487").unwrap(), + ResourceValidationMode::Rfc6487 + ); + assert!(ResourceValidationMode::parse_cli_value("bogus").is_err()); + } + + #[test] + fn policy_toml_accepts_strict_table() { + let policy = Policy::from_toml_str( + r#" + [strict] + name = true + cms_der = true + signed_attrs = false + "#, + ) + .expect("parse policy"); + assert_eq!( + policy.strict, + StrictPolicy { + name: true, + cms_der: true, + signed_attrs: false, + } + ); + } + + #[test] + fn policy_toml_accepts_resource_validation_mode() { + let policy = Policy::from_toml_str( + r#" + resource_validation_mode = "rfc6487" + "#, + ) + .expect("parse policy"); + assert_eq!( + policy.resource_validation_mode, + ResourceValidationMode::Rfc6487 + ); + } +} diff --git a/src/validation/publication_point.rs b/src/validation/publication_point.rs new file mode 100644 index 0000000..1297b16 --- /dev/null +++ b/src/validation/publication_point.rs @@ -0,0 +1,18 @@ +use crate::repository::storage::{PackFile, PackTime}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PublicationPointSnapshot { + pub format_version: u32, + pub manifest_rsync_uri: String, + pub publication_point_rsync_uri: String, + pub manifest_number_be: Vec, + pub this_update: PackTime, + pub next_update: PackTime, + pub verified_at: PackTime, + pub manifest_bytes: Vec, + pub files: Vec, +} + +impl PublicationPointSnapshot { + pub const FORMAT_VERSION_V1: u32 = 1; +} diff --git a/src/validation/run.rs b/src/validation/run.rs new file mode 100644 index 0000000..c13472d --- /dev/null +++ b/src/validation/run.rs @@ -0,0 +1,86 @@ +#![allow(clippy::too_many_arguments)] + +use crate::model::rc::{AsResourceSet, IpResourceSet}; +use crate::output::report::Warning; +use crate::repository::fetch::rsync::RsyncFetcher; +use crate::repository::storage::RocksStore; +use crate::repository::sync::rrdp::Fetcher as HttpFetcher; +use crate::validation::manifest::PublicationPointSource; +use crate::validation::objects::ObjectsOutput; +use crate::validation::policy::Policy; +use crate::validation::tree::{CaCertificateRef, CaInstanceHandle, PublicationPointRunner}; +use crate::validation::tree_runner::Rpkiv1PublicationPointRunner; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RunOutput { + pub publication_point_source: PublicationPointSource, + pub publication_point_warnings: Vec, + pub objects: ObjectsOutput, +} + +#[derive(Debug, thiserror::Error)] +pub enum RunError { + #[error("publication point runner failed: {0}")] + Runner(String), +} + +/// Offline-friendly end-to-end execution for a single publication point. +/// +/// This reuses the same fresh-first runtime path as the tree runner: +/// 1) repository sync (RRDP or rsync fallback) into the current repository view +/// 2) manifest processing into a validated publication point result +/// 3) signed object processing (ROA/ASPA) and public report/CCR output +pub fn run_publication_point_once( + store: &RocksStore, + policy: &Policy, + rrdp_notification_uri: Option<&str>, + rsync_base_uri: &str, + manifest_rsync_uri: &str, + publication_point_rsync_uri: &str, + http_fetcher: &dyn HttpFetcher, + rsync_fetcher: &dyn RsyncFetcher, + issuer_ca_der: &[u8], + issuer_ca_rsync_uri: Option<&str>, + issuer_effective_ip: Option<&IpResourceSet>, + issuer_effective_as: Option<&AsResourceSet>, + validation_time: time::OffsetDateTime, +) -> Result { + let handle = CaInstanceHandle { + depth: 0, + tal_id: "single-publication-point".to_string(), + parent_manifest_rsync_uri: None, + ca_certificate: CaCertificateRef::inline_der(issuer_ca_der.to_vec()), + ca_certificate_rsync_uri: issuer_ca_rsync_uri.map(str::to_string), + effective_ip_resources: issuer_effective_ip.cloned(), + effective_as_resources: issuer_effective_as.cloned(), + rsync_base_uri: rsync_base_uri.to_string(), + manifest_rsync_uri: manifest_rsync_uri.to_string(), + publication_point_rsync_uri: publication_point_rsync_uri.to_string(), + rrdp_notification_uri: rrdp_notification_uri.map(str::to_string), + }; + + let runner = Rpkiv1PublicationPointRunner { + store, + policy, + http_fetcher, + rsync_fetcher, + validation_time, + timing: None, + download_log: None, + current_repo_index: None, + repo_sync_runtime: None, + parallel_phase2_config: None, + parallel_roa_worker_pool: None, + ccr_accumulator: None, + }; + + let result = runner + .run_publication_point(&handle) + .map_err(RunError::Runner)?; + + Ok(RunOutput { + publication_point_source: result.source, + publication_point_warnings: result.warnings, + objects: result.objects, + }) +} diff --git a/src/validation/run_tree_from_tal.rs b/src/validation/run_tree_from_tal.rs new file mode 100644 index 0000000..d3d58ff --- /dev/null +++ b/src/validation/run_tree_from_tal.rs @@ -0,0 +1,42 @@ +#![allow(clippy::too_many_arguments, clippy::type_complexity)] + +use url::Url; + +use crate::ccr::CcrAccumulator; +use crate::model::ta::TrustAnchor; +use crate::output::analysis::timing::TimingHandle; +use crate::output::audit::PublicationPointAudit; +use crate::output::audit_downloads::DownloadLogHandle; +use crate::repository::current_repo_index::{CurrentRepoIndexHandle, CurrentRepoObject}; +use crate::repository::sync::rrdp::Fetcher; +use crate::scheduler::config::{ParallelPhase1Config, ParallelPhase2Config}; +use crate::scheduler::repo_runtime::{Phase1RepoSyncRuntime, RepoSyncRuntime}; +use crate::scheduler::repo_worker::{ + LiveRepoTransportExecutor, RepoTransportWorkerPool, RepoWorkerPoolConfig, +}; +use crate::scheduler::run_coordinator::GlobalRunCoordinator; +use crate::scheduler::types::{TalInputSpec, TalSource}; +use crate::validation::from_tal::{ + DiscoveredRootCaInstance, FromTalError, canonical_tal_rsync_uri_from_bytes, + discover_root_ca_instance_from_tal_and_ta_der, + discover_root_ca_instance_from_tal_and_ta_der_with_strict_name, + discover_root_ca_instance_from_tal_url_with_fetchers, + discover_root_ca_instance_from_tal_url_with_fetchers_strict_name, + discover_root_ca_instance_from_tal_with_fetchers, + discover_root_ca_instance_from_tal_with_fetchers_strict_name, +}; +use crate::validation::objects::ParallelRoaWorkerPool; +use crate::validation::tree::CaCertificateRef; +use crate::validation::tree::{ + CaInstanceHandle, TreeRunAuditOutput, TreeRunConfig, TreeRunError, TreeRunOutput, + run_tree_serial, run_tree_serial_audit, run_tree_serial_audit_multi_root, +}; +use crate::validation::tree_parallel::{ + run_tree_parallel_phase2_audit, run_tree_parallel_phase2_audit_multi_root, +}; +use crate::validation::tree_runner::Rpkiv1PublicationPointRunner; +use std::sync::{Arc, Mutex}; + +include!("run_tree_from_tal/discovery.rs"); +include!("run_tree_from_tal/serial.rs"); +include!("run_tree_from_tal/phase1.rs"); diff --git a/src/validation/run_tree_from_tal/discovery.rs b/src/validation/run_tree_from_tal/discovery.rs new file mode 100644 index 0000000..3d98707 --- /dev/null +++ b/src/validation/run_tree_from_tal/discovery.rs @@ -0,0 +1,360 @@ +fn tal_id_from_url_like(s: &str) -> Option { + let url = Url::parse(s).ok()?; + if let Some(last) = url + .path_segments() + .and_then(|mut segments| segments.rfind(|seg| !seg.is_empty())) + { + let stem = last.rsplit_once('.').map(|(stem, _)| stem).unwrap_or(last); + let trimmed = stem.trim(); + if !trimmed.is_empty() { + return Some(trimmed.to_string()); + } + } + url.host_str().map(|host| host.to_string()) +} + +fn derive_tal_id(discovery: &DiscoveredRootCaInstance) -> String { + discovery + .tal_url + .as_deref() + .and_then(tal_id_from_url_like) + .or_else(|| { + discovery + .trust_anchor + .resolved_ta_uri + .as_ref() + .and_then(|uri| tal_id_from_url_like(uri.as_str())) + }) + .or_else(|| { + discovery + .trust_anchor + .tal + .ta_uris + .first() + .and_then(|uri| tal_id_from_url_like(uri.as_str())) + }) + .unwrap_or_else(|| "unknown-tal".to_string()) +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RunTreeFromTalOutput { + pub discovery: DiscoveredRootCaInstance, + pub tree: TreeRunOutput, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RunTreeFromTalAuditOutput { + pub discovery: DiscoveredRootCaInstance, + pub discoveries: Vec, + pub successful_tal_inputs: Vec, + pub tree: TreeRunOutput, + pub publication_points: Vec, + pub downloads: Vec, + pub download_stats: crate::output::audit::AuditDownloadStats, + pub current_repo_objects: Vec, + pub ccr_accumulator: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TalRootDiscovery { + pub tal_input: TalInputSpec, + pub discovery: DiscoveredRootCaInstance, + pub root_handle: CaInstanceHandle, +} + +fn snapshot_current_repo_objects( + current_repo_index: Option<&CurrentRepoIndexHandle>, + collect: bool, +) -> Vec { + if !collect { + return Vec::new(); + } + current_repo_index + .and_then(|handle| handle.read().ok().map(|idx| idx.snapshot_objects())) + .unwrap_or_default() +} + +fn make_live_runner<'a>( + store: &'a crate::repository::storage::RocksStore, + policy: &'a crate::validation::policy::Policy, + http_fetcher: &'a dyn Fetcher, + rsync_fetcher: &'a dyn crate::repository::fetch::rsync::RsyncFetcher, + validation_time: time::OffsetDateTime, + timing: Option, + download_log: Option, + current_repo_index: Option, + repo_sync_runtime: Option>, + parallel_phase2_config: Option, + ccr_accumulator: Option, +) -> Rpkiv1PublicationPointRunner<'a> { + let parallel_roa_worker_pool = parallel_phase2_config + .as_ref() + .and_then(|config| ParallelRoaWorkerPool::new(config).ok()); + Rpkiv1PublicationPointRunner { + store, + policy, + http_fetcher, + rsync_fetcher, + validation_time, + timing, + download_log, + current_repo_index, + repo_sync_runtime, + parallel_phase2_config, + parallel_roa_worker_pool, + ccr_accumulator: ccr_accumulator.map(Mutex::new), + } +} + +fn build_phase1_repo_sync_runtime( + store: Arc, + policy: &crate::validation::policy::Policy, + http_fetcher: &H, + rsync_fetcher: &R, + parallel_config: ParallelPhase1Config, + timing: Option, + download_log: Option, + tal_inputs: Vec, +) -> Result<(Arc, CurrentRepoIndexHandle), RunTreeFromTalError> +where + H: Fetcher + Clone + 'static, + R: crate::repository::fetch::rsync::RsyncFetcher + Clone + 'static, +{ + let coordinator = GlobalRunCoordinator::new(parallel_config.clone(), tal_inputs); + let current_repo_index = coordinator.current_repo_index_handle(); + let rsync_fetcher_arc = Arc::new(rsync_fetcher.clone()); + let executor = LiveRepoTransportExecutor::new( + Arc::clone(&store), + current_repo_index.clone(), + Arc::new(http_fetcher.clone()), + Arc::clone(&rsync_fetcher_arc), + timing.clone(), + download_log, + ); + let pool = RepoTransportWorkerPool::new(RepoWorkerPoolConfig::from(¶llel_config), executor) + .map_err(RunTreeFromTalError::Setup)?; + let resolver_rsync_fetcher_arc = Arc::clone(&rsync_fetcher_arc); + let resolver: Arc String + Send + Sync> = + Arc::new(move |base: &str| resolver_rsync_fetcher_arc.dedup_key(base)); + let failure_rsync_fetcher_arc = Arc::clone(&rsync_fetcher_arc); + let failure_resolver: Arc Option + Send + Sync> = + Arc::new(move |base: &str| failure_rsync_fetcher_arc.failure_dedup_key(base)); + let _ = policy; // policy reserved for later runtime-level decisions + let runtime = Arc::new(Phase1RepoSyncRuntime::new_with_failure_scope( + coordinator, + pool, + resolver, + failure_resolver, + policy.sync_preference, + )); + Ok((runtime, current_repo_index)) +} + +fn root_discovery_from_tal_input( + tal_input: &TalInputSpec, + http_fetcher: &dyn Fetcher, + rsync_fetcher: &dyn crate::repository::fetch::rsync::RsyncFetcher, + strict_name: bool, +) -> Result { + match &tal_input.source { + TalSource::Url(url) => { + if strict_name { + discover_root_ca_instance_from_tal_url_with_fetchers_strict_name( + http_fetcher, + rsync_fetcher, + url, + ) + } else { + discover_root_ca_instance_from_tal_url_with_fetchers( + http_fetcher, + rsync_fetcher, + url, + ) + } + } + TalSource::DerBytes { + tal_bytes, ta_der, .. + } => { + if strict_name { + discover_root_ca_instance_from_tal_and_ta_der_with_strict_name( + tal_bytes, ta_der, None, + ) + } else { + discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, None) + } + } + TalSource::FilePath(path) => { + let tal_bytes = std::fs::read(path).map_err(|e| { + FromTalError::TalFetch(format!("read TAL file failed: {}: {e}", path.display())) + })?; + let tal = crate::model::tal::Tal::decode_bytes(&tal_bytes) + .map_err(FromTalError::from)?; + if strict_name { + discover_root_ca_instance_from_tal_with_fetchers_strict_name( + http_fetcher, + rsync_fetcher, + tal, + None, + ) + } else { + discover_root_ca_instance_from_tal_with_fetchers( + http_fetcher, + rsync_fetcher, + tal, + None, + ) + } + } + TalSource::FilePathWithTa { tal_path, ta_path } => { + let tal_bytes = std::fs::read(tal_path).map_err(|e| { + FromTalError::TalFetch(format!("read TAL file failed: {}: {e}", tal_path.display())) + })?; + let ta_der = std::fs::read(ta_path).map_err(|e| { + FromTalError::TaFetch(format!("read TA file failed: {}: {e}", ta_path.display())) + })?; + let resolved_ta_uri = canonical_tal_rsync_uri_from_bytes(&tal_bytes)?; + if strict_name { + discover_root_ca_instance_from_tal_and_ta_der_with_strict_name( + &tal_bytes, + &ta_der, + Some(&resolved_ta_uri), + ) + } else { + discover_root_ca_instance_from_tal_and_ta_der( + &tal_bytes, + &ta_der, + Some(&resolved_ta_uri), + ) + } + } + } +} + +fn discover_root_ca_instance_from_tal_url_with_policy( + policy: &crate::validation::policy::Policy, + http_fetcher: &dyn Fetcher, + rsync_fetcher: &dyn crate::repository::fetch::rsync::RsyncFetcher, + tal_url: &str, +) -> Result { + if policy.strict.name { + discover_root_ca_instance_from_tal_url_with_fetchers_strict_name( + http_fetcher, + rsync_fetcher, + tal_url, + ) + } else { + discover_root_ca_instance_from_tal_url_with_fetchers(http_fetcher, rsync_fetcher, tal_url) + } +} + +fn discover_root_ca_instance_from_tal_and_ta_der_with_policy( + policy: &crate::validation::policy::Policy, + tal_bytes: &[u8], + ta_der: &[u8], + resolved_ta_uri: Option<&Url>, +) -> Result { + if policy.strict.name { + discover_root_ca_instance_from_tal_and_ta_der_with_strict_name( + tal_bytes, + ta_der, + resolved_ta_uri, + ) + } else { + discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, resolved_ta_uri) + } +} + +fn discover_multiple_roots_from_tal_inputs( + tal_inputs: &[TalInputSpec], + http_fetcher: &dyn Fetcher, + rsync_fetcher: &dyn crate::repository::fetch::rsync::RsyncFetcher, + strict_name: bool, +) -> Result, RunTreeFromTalError> { + let mut roots = Vec::with_capacity(tal_inputs.len()); + for tal_input in tal_inputs { + let discovery = match root_discovery_from_tal_input( + tal_input, + http_fetcher, + rsync_fetcher, + strict_name, + ) { + Ok(discovery) => discovery, + Err(error) + if should_isolate_multi_tal_strict_name_failure( + tal_inputs.len(), + strict_name, + &error, + ) => + { + crate::logging::warning!( + "warning: skipping TAL '{}' because strict name validation failed during trust anchor discovery: {error}", + tal_input.tal_id + ); + continue; + } + Err(error) => return Err(error.into()), + }; + let root_handle = root_handle_from_trust_anchor( + &discovery.trust_anchor, + tal_input.tal_id.clone(), + None, + &discovery.ca_instance, + ); + roots.push(TalRootDiscovery { + tal_input: tal_input.clone(), + discovery, + root_handle, + }); + } + if roots.is_empty() { + return Err(RunTreeFromTalError::Setup( + "multi-TAL root discovery returned no usable roots after strict name filtering" + .to_string(), + )); + } + Ok(roots) +} + +fn should_isolate_multi_tal_strict_name_failure( + tal_input_count: usize, + strict_name: bool, + error: &FromTalError, +) -> bool { + strict_name + && tal_input_count > 1 + && error.to_string().contains("Name strict validation failed") +} + +#[derive(Debug, thiserror::Error)] +pub enum RunTreeFromTalError { + #[error("{0}")] + FromTal(#[from] FromTalError), + + #[error("validation setup failed: {0}")] + Setup(String), + + #[error("{0}")] + Tree(#[from] TreeRunError), +} + +pub fn root_handle_from_trust_anchor( + trust_anchor: &TrustAnchor, + tal_id: String, + ca_certificate_rsync_uri: Option, + ca_instance: &crate::validation::ca_instance::CaInstanceUris, +) -> CaInstanceHandle { + let ta_rc = trust_anchor.ta_certificate.rc_ca.clone(); + CaInstanceHandle { + depth: 0, + tal_id, + parent_manifest_rsync_uri: None, + ca_certificate: CaCertificateRef::inline_der(trust_anchor.ta_certificate.raw_der.clone()), + ca_certificate_rsync_uri, + effective_ip_resources: ta_rc.tbs.extensions.ip_resources.clone(), + effective_as_resources: ta_rc.tbs.extensions.as_resources.clone(), + rsync_base_uri: ca_instance.rsync_base_uri.clone(), + manifest_rsync_uri: ca_instance.manifest_rsync_uri.clone(), + publication_point_rsync_uri: ca_instance.publication_point_rsync_uri.clone(), + rrdp_notification_uri: ca_instance.rrdp_notification_uri.clone(), + } +} diff --git a/src/validation/run_tree_from_tal/phase1.rs b/src/validation/run_tree_from_tal/phase1.rs new file mode 100644 index 0000000..eefbcfb --- /dev/null +++ b/src/validation/run_tree_from_tal/phase1.rs @@ -0,0 +1,365 @@ +pub fn run_tree_from_tal_url_parallel_phase1_audit( + store: Arc, + policy: &crate::validation::policy::Policy, + tal_url: &str, + http_fetcher: &H, + rsync_fetcher: &R, + validation_time: time::OffsetDateTime, + config: &TreeRunConfig, + parallel_config: ParallelPhase1Config, + collect_current_repo_objects: bool, +) -> Result +where + H: Fetcher + Clone + 'static, + R: crate::repository::fetch::rsync::RsyncFetcher + Clone + 'static, +{ + let discovery = discover_root_ca_instance_from_tal_url_with_policy( + policy, + http_fetcher, + rsync_fetcher, + tal_url, + )?; + run_single_root_parallel_audit_inner( + store, + policy, + discovery, + vec![TalInputSpec::from_url(tal_url.to_string())], + http_fetcher, + rsync_fetcher, + validation_time, + config, + parallel_config, + None, + collect_current_repo_objects, + None, + ) +} + +pub fn run_tree_from_tal_and_ta_der_parallel_phase1_audit( + store: Arc, + policy: &crate::validation::policy::Policy, + tal_bytes: &[u8], + ta_der: &[u8], + resolved_ta_uri: Option<&url::Url>, + http_fetcher: &H, + rsync_fetcher: &R, + validation_time: time::OffsetDateTime, + config: &TreeRunConfig, + parallel_config: ParallelPhase1Config, + collect_current_repo_objects: bool, +) -> Result +where + H: Fetcher + Clone + 'static, + R: crate::repository::fetch::rsync::RsyncFetcher + Clone + 'static, +{ + let discovery = discover_root_ca_instance_from_tal_and_ta_der_with_policy( + policy, + tal_bytes, + ta_der, + resolved_ta_uri, + )?; + let derived_tal_id = derive_tal_id(&discovery); + let tal_inputs = vec![TalInputSpec { + tal_id: derived_tal_id.clone(), + rir_id: derived_tal_id, + source: TalSource::DerBytes { + tal_url: discovery + .tal_url + .clone() + .unwrap_or_else(|| "embedded-tal".to_string()), + tal_bytes: tal_bytes.to_vec(), + ta_der: ta_der.to_vec(), + }, + }]; + run_single_root_parallel_audit_inner( + store, + policy, + discovery, + tal_inputs, + http_fetcher, + rsync_fetcher, + validation_time, + config, + parallel_config, + None, + collect_current_repo_objects, + None, + ) +} + +pub fn run_tree_from_multiple_tals_parallel_phase1_audit( + store: Arc, + policy: &crate::validation::policy::Policy, + tal_inputs: Vec, + http_fetcher: &H, + rsync_fetcher: &R, + validation_time: time::OffsetDateTime, + config: &TreeRunConfig, + parallel_config: ParallelPhase1Config, + collect_current_repo_objects: bool, +) -> Result +where + H: Fetcher + Clone + 'static, + R: crate::repository::fetch::rsync::RsyncFetcher + Clone + 'static, +{ + run_multi_root_parallel_audit_inner( + store, + policy, + tal_inputs, + http_fetcher, + rsync_fetcher, + validation_time, + config, + parallel_config, + None, + collect_current_repo_objects, + None, + ) +} + +pub fn run_tree_from_tal_url_parallel_phase2_audit( + store: Arc, + policy: &crate::validation::policy::Policy, + tal_url: &str, + http_fetcher: &H, + rsync_fetcher: &R, + validation_time: time::OffsetDateTime, + config: &TreeRunConfig, + parallel_config: ParallelPhase1Config, + phase2_config: ParallelPhase2Config, + collect_current_repo_objects: bool, +) -> Result +where + H: Fetcher + Clone + 'static, + R: crate::repository::fetch::rsync::RsyncFetcher + Clone + 'static, +{ + let discovery = discover_root_ca_instance_from_tal_url_with_policy( + policy, + http_fetcher, + rsync_fetcher, + tal_url, + )?; + run_single_root_parallel_audit_inner( + store, + policy, + discovery, + vec![TalInputSpec::from_url(tal_url.to_string())], + http_fetcher, + rsync_fetcher, + validation_time, + config, + parallel_config, + Some(phase2_config), + collect_current_repo_objects, + None, + ) +} + +pub fn run_tree_from_tal_url_parallel_phase2_audit_with_timing( + store: Arc, + policy: &crate::validation::policy::Policy, + tal_url: &str, + http_fetcher: &H, + rsync_fetcher: &R, + validation_time: time::OffsetDateTime, + config: &TreeRunConfig, + parallel_config: ParallelPhase1Config, + phase2_config: ParallelPhase2Config, + collect_current_repo_objects: bool, + timing: &TimingHandle, +) -> Result +where + H: Fetcher + Clone + 'static, + R: crate::repository::fetch::rsync::RsyncFetcher + Clone + 'static, +{ + let discovery = discover_root_ca_instance_from_tal_url_with_policy( + policy, + http_fetcher, + rsync_fetcher, + tal_url, + )?; + run_single_root_parallel_audit_inner( + store, + policy, + discovery, + vec![TalInputSpec::from_url(tal_url.to_string())], + http_fetcher, + rsync_fetcher, + validation_time, + config, + parallel_config, + Some(phase2_config), + collect_current_repo_objects, + Some(timing.clone()), + ) +} + +pub fn run_tree_from_tal_and_ta_der_parallel_phase2_audit( + store: Arc, + policy: &crate::validation::policy::Policy, + tal_bytes: &[u8], + ta_der: &[u8], + resolved_ta_uri: Option<&url::Url>, + http_fetcher: &H, + rsync_fetcher: &R, + validation_time: time::OffsetDateTime, + config: &TreeRunConfig, + parallel_config: ParallelPhase1Config, + phase2_config: ParallelPhase2Config, + collect_current_repo_objects: bool, +) -> Result +where + H: Fetcher + Clone + 'static, + R: crate::repository::fetch::rsync::RsyncFetcher + Clone + 'static, +{ + let discovery = discover_root_ca_instance_from_tal_and_ta_der_with_policy( + policy, + tal_bytes, + ta_der, + resolved_ta_uri, + )?; + let derived_tal_id = derive_tal_id(&discovery); + let tal_inputs = vec![TalInputSpec { + tal_id: derived_tal_id.clone(), + rir_id: derived_tal_id, + source: TalSource::DerBytes { + tal_url: discovery + .tal_url + .clone() + .unwrap_or_else(|| "embedded-tal".to_string()), + tal_bytes: tal_bytes.to_vec(), + ta_der: ta_der.to_vec(), + }, + }]; + run_single_root_parallel_audit_inner( + store, + policy, + discovery, + tal_inputs, + http_fetcher, + rsync_fetcher, + validation_time, + config, + parallel_config, + Some(phase2_config), + collect_current_repo_objects, + None, + ) +} + +pub fn run_tree_from_tal_and_ta_der_parallel_phase2_audit_with_timing( + store: Arc, + policy: &crate::validation::policy::Policy, + tal_bytes: &[u8], + ta_der: &[u8], + resolved_ta_uri: Option<&url::Url>, + http_fetcher: &H, + rsync_fetcher: &R, + validation_time: time::OffsetDateTime, + config: &TreeRunConfig, + parallel_config: ParallelPhase1Config, + phase2_config: ParallelPhase2Config, + collect_current_repo_objects: bool, + timing: &TimingHandle, +) -> Result +where + H: Fetcher + Clone + 'static, + R: crate::repository::fetch::rsync::RsyncFetcher + Clone + 'static, +{ + let discovery = discover_root_ca_instance_from_tal_and_ta_der_with_policy( + policy, + tal_bytes, + ta_der, + resolved_ta_uri, + )?; + let derived_tal_id = derive_tal_id(&discovery); + let tal_inputs = vec![TalInputSpec { + tal_id: derived_tal_id.clone(), + rir_id: derived_tal_id, + source: TalSource::DerBytes { + tal_url: discovery + .tal_url + .clone() + .unwrap_or_else(|| "embedded-tal".to_string()), + tal_bytes: tal_bytes.to_vec(), + ta_der: ta_der.to_vec(), + }, + }]; + run_single_root_parallel_audit_inner( + store, + policy, + discovery, + tal_inputs, + http_fetcher, + rsync_fetcher, + validation_time, + config, + parallel_config, + Some(phase2_config), + collect_current_repo_objects, + Some(timing.clone()), + ) +} + +pub fn run_tree_from_multiple_tals_parallel_phase2_audit( + store: Arc, + policy: &crate::validation::policy::Policy, + tal_inputs: Vec, + http_fetcher: &H, + rsync_fetcher: &R, + validation_time: time::OffsetDateTime, + config: &TreeRunConfig, + parallel_config: ParallelPhase1Config, + phase2_config: ParallelPhase2Config, + collect_current_repo_objects: bool, +) -> Result +where + H: Fetcher + Clone + 'static, + R: crate::repository::fetch::rsync::RsyncFetcher + Clone + 'static, +{ + run_multi_root_parallel_audit_inner( + store, + policy, + tal_inputs, + http_fetcher, + rsync_fetcher, + validation_time, + config, + parallel_config, + Some(phase2_config), + collect_current_repo_objects, + None, + ) +} + +pub fn run_tree_from_multiple_tals_parallel_phase2_audit_with_timing( + store: Arc, + policy: &crate::validation::policy::Policy, + tal_inputs: Vec, + http_fetcher: &H, + rsync_fetcher: &R, + validation_time: time::OffsetDateTime, + config: &TreeRunConfig, + parallel_config: ParallelPhase1Config, + phase2_config: ParallelPhase2Config, + collect_current_repo_objects: bool, + timing: &TimingHandle, +) -> Result +where + H: Fetcher + Clone + 'static, + R: crate::repository::fetch::rsync::RsyncFetcher + Clone + 'static, +{ + run_multi_root_parallel_audit_inner( + store, + policy, + tal_inputs, + http_fetcher, + rsync_fetcher, + validation_time, + config, + parallel_config, + Some(phase2_config), + collect_current_repo_objects, + Some(timing.clone()), + ) +} diff --git a/src/validation/run_tree_from_tal/serial.rs b/src/validation/run_tree_from_tal/serial.rs new file mode 100644 index 0000000..f23361b --- /dev/null +++ b/src/validation/run_tree_from_tal/serial.rs @@ -0,0 +1,344 @@ +pub fn run_tree_from_tal_url_serial( + store: &crate::repository::storage::RocksStore, + policy: &crate::validation::policy::Policy, + tal_url: &str, + http_fetcher: &dyn Fetcher, + rsync_fetcher: &dyn crate::repository::fetch::rsync::RsyncFetcher, + validation_time: time::OffsetDateTime, + config: &TreeRunConfig, +) -> Result { + let discovery = discover_root_ca_instance_from_tal_url_with_policy( + policy, + http_fetcher, + rsync_fetcher, + tal_url, + )?; + + let runner = make_live_runner( + store, + policy, + http_fetcher, + rsync_fetcher, + validation_time, + None, + None, + None, + None, + None, + None, + ); + + let root = root_handle_from_trust_anchor( + &discovery.trust_anchor, + derive_tal_id(&discovery), + None, + &discovery.ca_instance, + ); + let tree = run_tree_serial(root, &runner, config)?; + + Ok(RunTreeFromTalOutput { discovery, tree }) +} + +pub fn run_tree_from_tal_url_serial_audit( + store: &crate::repository::storage::RocksStore, + policy: &crate::validation::policy::Policy, + tal_url: &str, + http_fetcher: &dyn Fetcher, + rsync_fetcher: &dyn crate::repository::fetch::rsync::RsyncFetcher, + validation_time: time::OffsetDateTime, + config: &TreeRunConfig, +) -> Result { + let discovery = discover_root_ca_instance_from_tal_url_with_policy( + policy, + http_fetcher, + rsync_fetcher, + tal_url, + )?; + + let download_log = DownloadLogHandle::new(); + let runner = make_live_runner( + store, + policy, + http_fetcher, + rsync_fetcher, + validation_time, + None, + Some(download_log.clone()), + None, + None, + None, + None, + ); + + let root = root_handle_from_trust_anchor( + &discovery.trust_anchor, + derive_tal_id(&discovery), + None, + &discovery.ca_instance, + ); + let TreeRunAuditOutput { + tree, + publication_points, + } = run_tree_serial_audit(root, &runner, config)?; + + let downloads = download_log.snapshot_events(); + let download_stats = DownloadLogHandle::stats_from_events(&downloads); + Ok(RunTreeFromTalAuditOutput { + discovery: discovery.clone(), + discoveries: vec![discovery], + successful_tal_inputs: Vec::new(), + tree, + publication_points, + downloads, + download_stats, + current_repo_objects: Vec::new(), + ccr_accumulator: None, + }) +} + +pub fn run_tree_from_tal_url_serial_audit_with_timing( + store: &crate::repository::storage::RocksStore, + policy: &crate::validation::policy::Policy, + tal_url: &str, + http_fetcher: &dyn Fetcher, + rsync_fetcher: &dyn crate::repository::fetch::rsync::RsyncFetcher, + validation_time: time::OffsetDateTime, + config: &TreeRunConfig, + timing: &TimingHandle, +) -> Result { + let _tal = timing.span_phase("tal_bootstrap"); + let discovery = discover_root_ca_instance_from_tal_url_with_policy( + policy, + http_fetcher, + rsync_fetcher, + tal_url, + )?; + drop(_tal); + + let download_log = DownloadLogHandle::new(); + let runner = make_live_runner( + store, + policy, + http_fetcher, + rsync_fetcher, + validation_time, + Some(timing.clone()), + Some(download_log.clone()), + None, + None, + None, + None, + ); + + let root = root_handle_from_trust_anchor( + &discovery.trust_anchor, + derive_tal_id(&discovery), + None, + &discovery.ca_instance, + ); + let _tree = timing.span_phase("tree_run_total"); + let TreeRunAuditOutput { + tree, + publication_points, + } = run_tree_serial_audit(root, &runner, config)?; + + let downloads = download_log.snapshot_events(); + let download_stats = DownloadLogHandle::stats_from_events(&downloads); + Ok(RunTreeFromTalAuditOutput { + discovery: discovery.clone(), + discoveries: vec![discovery], + successful_tal_inputs: Vec::new(), + tree, + publication_points, + downloads, + download_stats, + current_repo_objects: Vec::new(), + ccr_accumulator: None, + }) +} + +fn run_single_root_parallel_audit_inner( + store: Arc, + policy: &crate::validation::policy::Policy, + discovery: DiscoveredRootCaInstance, + tal_inputs: Vec, + http_fetcher: &H, + rsync_fetcher: &R, + validation_time: time::OffsetDateTime, + config: &TreeRunConfig, + parallel_config: ParallelPhase1Config, + phase2_config: Option, + collect_current_repo_objects: bool, + timing: Option, +) -> Result +where + H: Fetcher + Clone + 'static, + R: crate::repository::fetch::rsync::RsyncFetcher + Clone + 'static, +{ + let phase2_enabled = phase2_config.is_some(); + let download_log = DownloadLogHandle::new(); + let (runtime, current_repo_index) = build_phase1_repo_sync_runtime( + Arc::clone(&store), + policy, + http_fetcher, + rsync_fetcher, + parallel_config, + timing.clone(), + Some(download_log.clone()), + tal_inputs, + )?; + let current_repo_index_for_output = current_repo_index.clone(); + let runner = make_live_runner( + store.as_ref(), + policy, + http_fetcher, + rsync_fetcher, + validation_time, + timing.clone(), + Some(download_log.clone()), + Some(current_repo_index), + Some(Arc::clone(&runtime)), + phase2_config, + (phase2_enabled && config.build_ccr_accumulator) + .then(|| CcrAccumulator::new(vec![discovery.trust_anchor.clone()])), + ); + + let root = root_handle_from_trust_anchor( + &discovery.trust_anchor, + derive_tal_id(&discovery), + None, + &discovery.ca_instance, + ); + let TreeRunAuditOutput { + tree, + publication_points, + } = if phase2_enabled { + run_tree_parallel_phase2_audit(root, &runner, config)? + } else { + run_tree_serial_audit(root, &runner, config)? + }; + let downloads = download_log.snapshot_events(); + let download_stats = DownloadLogHandle::stats_from_events(&downloads); + Ok(RunTreeFromTalAuditOutput { + discovery: discovery.clone(), + discoveries: vec![discovery], + successful_tal_inputs: Vec::new(), + tree, + publication_points, + downloads, + download_stats, + current_repo_objects: snapshot_current_repo_objects( + Some(¤t_repo_index_for_output), + collect_current_repo_objects, + ), + ccr_accumulator: runner.ccr_accumulator_snapshot(), + }) +} + +fn run_multi_root_parallel_audit_inner( + store: Arc, + policy: &crate::validation::policy::Policy, + tal_inputs: Vec, + http_fetcher: &H, + rsync_fetcher: &R, + validation_time: time::OffsetDateTime, + config: &TreeRunConfig, + parallel_config: ParallelPhase1Config, + phase2_config: Option, + collect_current_repo_objects: bool, + timing: Option, +) -> Result +where + H: Fetcher + Clone + 'static, + R: crate::repository::fetch::rsync::RsyncFetcher + Clone + 'static, +{ + // Constraints are an immutable per-run policy snapshot. The phase-2 + // ready-stage binds the snapshot to each CA's TAL and moves an Arc into + // ROA/ASPA worker state, so constrained multi-TAL runs retain the same + // parallel scheduler as unconstrained runs. + let phase2_enabled = phase2_config.is_some(); + if tal_inputs.is_empty() { + return Err(RunTreeFromTalError::Setup( + "multi-TAL run requires at least one TAL input".to_string(), + )); + } + let roots = discover_multiple_roots_from_tal_inputs( + &tal_inputs, + http_fetcher, + rsync_fetcher, + policy.strict.name, + )?; + let primary = roots.first().cloned().ok_or_else(|| { + RunTreeFromTalError::Setup("multi-TAL root discovery returned no roots".to_string()) + })?; + let discoveries = roots + .iter() + .map(|item| item.discovery.clone()) + .collect::>(); + let successful_tal_inputs = roots + .iter() + .map(|item| item.tal_input.clone()) + .collect::>(); + let root_handles = roots + .iter() + .map(|item| item.root_handle.clone()) + .collect::>(); + + let download_log = DownloadLogHandle::new(); + let (runtime, current_repo_index) = build_phase1_repo_sync_runtime( + Arc::clone(&store), + policy, + http_fetcher, + rsync_fetcher, + parallel_config, + timing.clone(), + Some(download_log.clone()), + successful_tal_inputs.clone(), + )?; + let current_repo_index_for_output = current_repo_index.clone(); + let runner = make_live_runner( + store.as_ref(), + policy, + http_fetcher, + rsync_fetcher, + validation_time, + timing.clone(), + Some(download_log.clone()), + Some(current_repo_index), + Some(Arc::clone(&runtime)), + phase2_config, + (phase2_enabled && config.build_ccr_accumulator).then(|| { + CcrAccumulator::new( + discoveries + .iter() + .map(|item| item.trust_anchor.clone()) + .collect::>(), + ) + }), + ); + + let TreeRunAuditOutput { + tree, + publication_points, + } = if phase2_enabled { + run_tree_parallel_phase2_audit_multi_root(root_handles, &runner, config)? + } else { + run_tree_serial_audit_multi_root(root_handles, &runner, config)? + }; + let downloads = download_log.snapshot_events(); + let download_stats = DownloadLogHandle::stats_from_events(&downloads); + Ok(RunTreeFromTalAuditOutput { + discovery: primary.discovery.clone(), + discoveries, + successful_tal_inputs, + tree, + publication_points, + downloads, + download_stats, + current_repo_objects: snapshot_current_repo_objects( + Some(¤t_repo_index_for_output), + collect_current_repo_objects, + ), + ccr_accumulator: runner.ccr_accumulator_snapshot(), + }) +} diff --git a/src/validation/tree.rs b/src/validation/tree.rs new file mode 100644 index 0000000..0646455 --- /dev/null +++ b/src/validation/tree.rs @@ -0,0 +1,363 @@ +use crate::model::rc::{AsResourceSet, IpResourceSet}; +use crate::output::audit::{DiscoveredFrom, PublicationPointAudit}; +use crate::output::report::Warning; +use crate::validation::manifest::PublicationPointSource; +use crate::validation::objects::{AspaAttestation, ObjectsOutput, RouterKeyPayload, Vrp}; +use crate::validation::publication_point::PublicationPointSnapshot; +use std::borrow::Cow; +use std::sync::{Arc, OnceLock}; + +pub const DEFAULT_MAX_CA_DEPTH: usize = 32; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TreeRunConfig { + /// Max CA instance depth to process (0 = root only). + pub max_depth: Option, + /// Max number of CA instances to process. + pub max_instances: Option, + /// Drop per-object audit payload when the caller only needs validation outputs. + pub compact_audit: bool, + /// Build online CCR manifest projections during phase2 validation. + pub build_ccr_accumulator: bool, +} + +impl Default for TreeRunConfig { + fn default() -> Self { + Self { + max_depth: Some(DEFAULT_MAX_CA_DEPTH), + max_instances: None, + compact_audit: false, + build_ccr_accumulator: true, + } + } +} + +pub(crate) fn ca_depth_is_allowed(config: &TreeRunConfig, depth: usize) -> bool { + config.max_depth.is_none_or(|max_depth| depth <= max_depth) +} + +pub(crate) fn next_allowed_ca_depth(config: &TreeRunConfig, parent_depth: usize) -> Option { + let child_depth = parent_depth.checked_add(1)?; + ca_depth_is_allowed(config, child_depth).then_some(child_depth) +} + +#[derive(Clone, Debug)] +pub enum CaCertificateRef { + InlineDer(Vec), + RepoBytes { + sha256_hex: String, + cached_der: Arc>>, + }, +} + +impl PartialEq for CaCertificateRef { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::InlineDer(left), Self::InlineDer(right)) => left == right, + ( + Self::RepoBytes { + sha256_hex: left, .. + }, + Self::RepoBytes { + sha256_hex: right, .. + }, + ) => left == right, + _ => false, + } + } +} + +impl Eq for CaCertificateRef {} + +impl CaCertificateRef { + pub fn inline_der(bytes: Vec) -> Self { + Self::InlineDer(bytes) + } + + pub fn repo_bytes(sha256_hex: String) -> Self { + Self::RepoBytes { + sha256_hex, + cached_der: Arc::new(OnceLock::new()), + } + } + + pub fn sha256_hex(&self) -> Option<&str> { + match self { + Self::InlineDer(_) => None, + Self::RepoBytes { sha256_hex, .. } => Some(sha256_hex.as_str()), + } + } + + pub fn der<'a>( + &'a self, + store: &crate::repository::storage::RocksStore, + ) -> Result, String> { + match self { + Self::InlineDer(bytes) => Ok(Cow::Borrowed(bytes.as_slice())), + Self::RepoBytes { + sha256_hex, + cached_der, + } => { + if cached_der.get().is_none() { + let bytes = store + .get_blob_bytes(sha256_hex) + .map_err(|e| format!("load CA certificate bytes failed: {e}"))? + .ok_or_else(|| { + format!("missing CA certificate repo bytes for sha256={sha256_hex}") + })?; + let _ = cached_der.set(Arc::from(bytes)); + } + let bytes = cached_der.get().ok_or_else(|| { + format!("missing cached CA certificate bytes for sha256={sha256_hex}") + })?; + Ok(Cow::Borrowed(bytes.as_ref())) + } + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CaInstanceHandle { + pub depth: usize, + pub tal_id: String, + pub parent_manifest_rsync_uri: Option, + /// CA certificate bytes or a lazy repo-bytes reference for this CA instance. + pub ca_certificate: CaCertificateRef, + /// rsync URI of this CA certificate object (where it is published). + /// + /// This is used for strict AIA binding checks (RFC 6487 §4.8.7) when validating + /// subordinate CA and EE certificates. Trust anchor certificates downloaded from + /// TAL URIs may not have an rsync publication URI, so this can be `None`. + pub ca_certificate_rsync_uri: Option, + /// Effective (fully resolved) resources for this CA instance, used for resource-path + /// validation of subordinate CA certificates. + pub effective_ip_resources: Option, + pub effective_as_resources: Option, + pub rsync_base_uri: String, + pub manifest_rsync_uri: String, + pub publication_point_rsync_uri: String, + pub rrdp_notification_uri: Option, +} + +impl CaInstanceHandle { + pub fn with_depth(mut self, depth: usize) -> Self { + self.depth = depth; + self + } + + pub fn ca_certificate_der<'a>( + &'a self, + store: &crate::repository::storage::RocksStore, + ) -> Result, String> { + self.ca_certificate.der(store) + } + + pub fn ca_certificate_sha256_hex(&self) -> Option<&str> { + self.ca_certificate.sha256_hex() + } + + pub fn ca_certificate_sha256_32(&self) -> Option<[u8; 32]> { + match &self.ca_certificate { + CaCertificateRef::InlineDer(bytes) => { + use sha2::Digest as _; + let digest = sha2::Sha256::digest(bytes); + Some(digest.into()) + } + CaCertificateRef::RepoBytes { sha256_hex, .. } => { + let mut out = [0u8; 32]; + hex::decode_to_slice(sha256_hex, &mut out).ok()?; + Some(out) + } + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PublicationPointRunResult { + pub source: PublicationPointSource, + pub snapshot: Option, + pub warnings: Vec, + pub objects: ObjectsOutput, + pub audit: PublicationPointAudit, + /// Candidate child CA instances to enqueue after this publication point completes. + /// + /// - For `Fresh`, these are discovered from the current validated publication point. + /// - For failed synchronization, the result contains no child instances; + /// callers may retry the repository on a later run. + pub discovered_children: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct DiscoveredChildEntryProjection { + pub child_ski: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct DiscoveredChildCaInstance { + pub handle: CaInstanceHandle, + pub discovered_from: DiscoveredFrom, + pub child_entry_projection: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TreeRunOutput { + pub instances_processed: usize, + pub instances_failed: usize, + pub warnings: Vec, + pub vrps: Vec, + pub aspas: Vec, + pub router_keys: Vec, +} + +#[derive(Debug, thiserror::Error)] +pub enum TreeRunError { + #[error("publication point runner failed: {0}")] + Runner(String), +} + +pub trait PublicationPointRunner { + fn run_publication_point( + &self, + ca: &CaInstanceHandle, + ) -> Result; +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TreeRunAuditOutput { + pub tree: TreeRunOutput, + pub publication_points: Vec, +} + +pub fn run_tree_serial( + root: CaInstanceHandle, + runner: &dyn PublicationPointRunner, + config: &TreeRunConfig, +) -> Result { + Ok(run_tree_serial_audit(root, runner, config)?.tree) +} + +pub fn run_tree_serial_audit( + root: CaInstanceHandle, + runner: &dyn PublicationPointRunner, + config: &TreeRunConfig, +) -> Result { + run_tree_serial_audit_multi_root(vec![root], runner, config) +} + +pub fn run_tree_serial_audit_multi_root( + roots: Vec, + runner: &dyn PublicationPointRunner, + config: &TreeRunConfig, +) -> Result { + #[derive(Clone, Debug)] + struct QueuedCaInstance { + id: u64, + handle: CaInstanceHandle, + parent_id: Option, + discovered_from: Option, + } + + let mut next_id: u64 = 0; + let mut queue: std::collections::VecDeque = std::collections::VecDeque::new(); + for root in roots { + queue.push_back(QueuedCaInstance { + id: next_id, + handle: root, + parent_id: None, + discovered_from: None, + }); + next_id += 1; + } + + let mut visited_manifest_uris: std::collections::HashSet = + std::collections::HashSet::new(); + let mut instances_processed = 0usize; + let mut instances_failed = 0usize; + let mut warnings: Vec = Vec::new(); + let mut vrps: Vec = Vec::new(); + let mut aspas: Vec = Vec::new(); + let mut router_keys: Vec = Vec::new(); + let mut publication_points: Vec = Vec::new(); + + while let Some(node) = queue.pop_front() { + let ca = &node.handle; + if !visited_manifest_uris.insert(ca.manifest_rsync_uri.clone()) { + continue; + } + + if let Some(max_instances) = config.max_instances + && instances_processed >= max_instances + { + break; + } + + if !ca_depth_is_allowed(config, ca.depth) { + continue; + } + + let res = match runner.run_publication_point(ca) { + Ok(v) => v, + Err(e) => { + instances_failed += 1; + warnings.push( + Warning::new(format!("publication point failed: {e}")) + .with_context(&ca.manifest_rsync_uri), + ); + continue; + } + }; + + instances_processed += 1; + warnings.extend(res.warnings); + warnings.extend(res.objects.warnings); + vrps.extend(res.objects.vrps); + aspas.extend(res.objects.aspas); + router_keys.extend(res.objects.router_keys); + + let mut audit = res.audit; + audit.node_id = Some(node.id); + audit.parent_node_id = node.parent_id; + audit.discovered_from = node.discovered_from.clone(); + if config.compact_audit { + audit.objects.clear(); + audit.warnings.clear(); + } + publication_points.push(audit); + + let Some(child_depth) = next_allowed_ca_depth(config, ca.depth) else { + continue; + }; + let mut children = res.discovered_children; + children.sort_by(|a, b| { + a.handle + .manifest_rsync_uri + .cmp(&b.handle.manifest_rsync_uri) + .then_with(|| { + a.discovered_from + .child_ca_certificate_rsync_uri + .cmp(&b.discovered_from.child_ca_certificate_rsync_uri) + }) + }); + for child in children { + queue.push_back(QueuedCaInstance { + id: next_id, + handle: child.handle.with_depth(child_depth), + parent_id: Some(node.id), + discovered_from: Some(child.discovered_from), + }); + next_id += 1; + } + } + + Ok(TreeRunAuditOutput { + tree: TreeRunOutput { + instances_processed, + instances_failed, + warnings, + vrps, + aspas, + router_keys, + }, + publication_points, + }) +} diff --git a/src/validation/tree_parallel.rs b/src/validation/tree_parallel.rs new file mode 100644 index 0000000..8a01570 --- /dev/null +++ b/src/validation/tree_parallel.rs @@ -0,0 +1,33 @@ +#![allow(clippy::too_many_arguments, clippy::large_enum_variant)] + +use std::collections::{HashMap, HashSet, VecDeque}; +use std::sync::mpsc::{self, Receiver, SyncSender, TryRecvError, TrySendError}; +use std::time::{Duration, Instant}; + +use crate::output::audit::{DiscoveredFrom, PublicationPointAudit}; +use crate::output::report::Warning; +use crate::scheduler::object_worker::{ + ObjectTaskExecutor, ObjectWorkerSubmitError, ScopedObjectWorkerPool, +}; +use crate::scheduler::repo_runtime::{RepoSyncRequestStatus, RepoSyncRuntimeOutcome}; +use crate::scheduler::types::RepoIdentity; +use crate::validation::manifest::PublicationPointData; +use crate::validation::objects::{ + ObjectsOutput, OwnedRoaTask, ParallelObjectsPrepare, ParallelObjectsStage, + prepare_publication_point_for_parallel_roa_and_ta_constraints, reduce_parallel_roa_stage, +}; +use crate::validation::policy::SignedObjectFailurePolicy; +use crate::validation::tree::{ + CaInstanceHandle, DiscoveredChildCaInstance, PublicationPointRunResult, PublicationPointRunner, + TreeRunAuditOutput, TreeRunConfig, TreeRunError, TreeRunOutput, ca_depth_is_allowed, + next_allowed_ca_depth, run_tree_serial_audit_multi_root, +}; +use crate::validation::tree_runner::{ + FreshPublicationPointFinalizeOutput, FreshPublicationPointStage, Rpkiv1PublicationPointRunner, +}; + +include!("tree_parallel/state.rs"); +include!("tree_parallel/phase2.rs"); +include!("tree_parallel/ready_stage.rs"); +include!("tree_parallel/dispatch.rs"); +include!("tree_parallel/finalize.rs"); diff --git a/src/validation/tree_parallel/dispatch.rs b/src/validation/tree_parallel/dispatch.rs new file mode 100644 index 0000000..a2f7be5 --- /dev/null +++ b/src/validation/tree_parallel/dispatch.rs @@ -0,0 +1,450 @@ +fn flush_pending_roa_dispatch( + runner: &Rpkiv1PublicationPointRunner<'_>, + pending_roa_dispatch: &mut VecDeque, + inflight_publication_points: &mut HashMap, +) -> Result { + let started = Instant::now(); + let mut metrics = RoaDispatchMetrics::default(); + let Some(pool) = runner.parallel_roa_worker_pool.as_ref() else { + return Ok(metrics); + }; + while let Some(mut task) = pending_roa_dispatch.pop_front() { + metrics.attempted += 1; + let pp_id = task.publication_point_id; + task.submitted_at = Some(Instant::now()); + match pool.try_submit_round_robin(task) { + Ok(_) => { + metrics.submitted += 1; + if let Some(state) = inflight_publication_points.get_mut(&pp_id) { + let now = Instant::now(); + state.tasks_submitted += 1; + if state.first_task_submitted_at.is_none() { + state.first_task_submitted_at = Some(now); + } + state.last_task_submitted_at = Some(now); + } + } + Err(ObjectWorkerSubmitError::QueueFull { task, .. }) => { + pending_roa_dispatch.push_front(task); + metrics.queue_full = true; + break; + } + Err(ObjectWorkerSubmitError::Disconnected { .. }) => { + return Err(TreeRunError::Runner( + "parallel ROA worker queue disconnected".to_string(), + )); + } + } + } + metrics.pending_remaining = pending_roa_dispatch.len(); + metrics.duration_ms = elapsed_ms(started); + Ok(metrics) +} + +fn flush_pending_roa_dispatch_with_progress( + runner: &Rpkiv1PublicationPointRunner<'_>, + pending_roa_dispatch: &mut VecDeque, + inflight_publication_points: &mut HashMap, + pending_finalization: &VecDeque, +) -> Result<(), TreeRunError> { + let dispatch_metrics = + flush_pending_roa_dispatch(runner, pending_roa_dispatch, inflight_publication_points)?; + if dispatch_metrics.attempted > 0 || dispatch_metrics.queue_full { + crate::logging::progress::emit!( + "phase2_roa_dispatch_batch", + serde_json::json!({ + "attempted": dispatch_metrics.attempted, + "submitted": dispatch_metrics.submitted, + "queue_full": dispatch_metrics.queue_full, + "pending_remaining": dispatch_metrics.pending_remaining, + "duration_ms": dispatch_metrics.duration_ms, + "inflight_publication_points": inflight_publication_points.len(), + "pending_finalization_len": pending_finalization.len(), + }), + ); + } + Ok(()) +} + +fn drain_object_results( + runner: &Rpkiv1PublicationPointRunner<'_>, + inflight_publication_points: &mut HashMap, + pending_finalization: &mut VecDeque, + result_budget: usize, +) -> Result { + let started = Instant::now(); + let mut metrics = ObjectDrainMetrics::default(); + let Some(pool) = runner.parallel_roa_worker_pool.as_ref() else { + return Ok(metrics); + }; + let result_budget = result_budget.max(1); + while metrics.results_drained < result_budget { + let Some(result) = pool + .recv_result_timeout(Duration::from_millis(0)) + .map_err(TreeRunError::Runner)? + else { + break; + }; + metrics.results_drained += 1; + let pp_id = result.publication_point_id; + let _worker_index = result.worker_index; + metrics.worker_ms_total += result.worker_ms; + metrics.worker_ms_max = metrics.worker_ms_max.max(result.worker_ms); + metrics.queue_wait_ms_total += result.queue_wait_ms; + metrics.queue_wait_ms_max = metrics.queue_wait_ms_max.max(result.queue_wait_ms); + let should_finalize = if let Some(state) = inflight_publication_points.get_mut(&pp_id) { + let now = Instant::now(); + if state.first_result_at.is_none() { + state.first_result_at = Some(now); + } + state.last_result_at = Some(now); + state.worker_ms_total += result.worker_ms; + state.worker_ms_max = state.worker_ms_max.max(result.worker_ms); + state.queue_wait_ms_total += result.queue_wait_ms; + state.queue_wait_ms_max = state.queue_wait_ms_max.max(result.queue_wait_ms); + state.results.push(result); + state.results.len() == state.task_count + } else { + false + }; + if should_finalize { + let mut state = inflight_publication_points + .remove(&pp_id) + .expect("inflight publication point must exist"); + state.finalize_enqueued_at = Some(Instant::now()); + metrics.publication_points_completed += 1; + pending_finalization.push_back(FinalizeTask { state }); + } + } + metrics.result_budget_exhausted = metrics.results_drained == result_budget; + metrics.duration_ms = elapsed_ms(started); + Ok(metrics) +} + +fn drain_object_results_with_progress( + runner: &Rpkiv1PublicationPointRunner<'_>, + inflight_publication_points: &mut HashMap, + pending_finalization: &mut VecDeque, + pending_roa_dispatch_len: usize, + result_budget: usize, +) -> Result<(), TreeRunError> { + let drain_metrics = drain_object_results( + runner, + inflight_publication_points, + pending_finalization, + result_budget, + )?; + if drain_metrics.results_drained > 0 || drain_metrics.result_budget_exhausted { + crate::logging::progress::emit!( + "phase2_object_results_drain", + serde_json::json!({ + "results_drained": drain_metrics.results_drained, + "publication_points_completed": drain_metrics.publication_points_completed, + "result_budget_exhausted": drain_metrics.result_budget_exhausted, + "result_drain_batch_size": result_budget, + "worker_ms_total": drain_metrics.worker_ms_total, + "worker_ms_max": drain_metrics.worker_ms_max, + "queue_wait_ms_total": drain_metrics.queue_wait_ms_total, + "queue_wait_ms_max": drain_metrics.queue_wait_ms_max, + "duration_ms": drain_metrics.duration_ms, + "pending_roa_dispatch_len": pending_roa_dispatch_len, + "inflight_publication_points": inflight_publication_points.len(), + "pending_finalization_len": pending_finalization.len(), + }), + ); + } + Ok(()) +} + +/// Submission endpoint of the stage pool, abstracted so the dispatch loop can +/// be tested with a deterministic backpressure source. +trait ReadyStageSubmitter { + // The error must hand the task back so the dispatch loop can requeue it; + // boxing it away would only shuffle the same bytes around. + #[allow(clippy::result_large_err)] + fn try_submit_ready_stage( + &mut self, + task: ReadyStageTask, + ) -> Result<(), ObjectWorkerSubmitError>; +} + +impl ReadyStageSubmitter for ReadyStagePool<'_, '_> { + fn try_submit_ready_stage( + &mut self, + task: ReadyStageTask, + ) -> Result<(), ObjectWorkerSubmitError> { + self.try_submit_round_robin(task).map(|_| ()) + } +} + +/// Dispatch loop of the pool-enabled ready batch: pop ready publication +/// points and submit their compute tasks to the stage pool, following the +/// `flush_pending_roa_dispatch` backpressure pattern — on a full worker queue +/// the publication point goes back to the head of the ready queue so the next +/// turn retries it first (nothing lost, nothing duplicated). +fn submit_ready_batch_to_stage_pool( + stage_pool: &mut impl ReadyStageSubmitter, + ready_queue: &mut VecDeque, + staging_inflight: &mut usize, + ready_batch_size: usize, + ready_batch_wall_time_budget: Duration, +) -> Result { + let started = Instant::now(); + let mut metrics = StageDispatchMetrics::default(); + while metrics.submitted < ready_batch_size { + let Some(ready) = ready_queue.pop_front() else { + break; + }; + let task = ReadyStageTask { + ready_queue_len_after_pop: ready_queue.len(), + ready, + submitted_at: Instant::now(), + }; + match stage_pool.try_submit_ready_stage(task) { + Ok(_) => { + *staging_inflight += 1; + metrics.submitted += 1; + } + Err(ObjectWorkerSubmitError::QueueFull { task, .. }) => { + ready_queue.push_front(task.ready); + metrics.queue_full = true; + break; + } + Err(ObjectWorkerSubmitError::Disconnected { .. }) => { + return Err(TreeRunError::Runner( + "ready stage worker queue disconnected".to_string(), + )); + } + } + if metrics.submitted > 0 && started.elapsed() >= ready_batch_wall_time_budget { + break; + } + } + metrics.duration_ms = elapsed_ms(started); + Ok(metrics) +} + +/// Collect every available stage result without blocking and run the apply +/// phase for each on the control thread, exactly like the inline path would +/// have done right after compute. Drained metrics aggregate into the same +/// `ReadyStageBatchMetrics`, keeping the `phase2_ready_queue_*` events +/// unchanged. +#[allow(clippy::too_many_arguments)] +fn drain_stage_results( + stage_pool: &ReadyStagePool<'_, '_>, + runner: &Rpkiv1PublicationPointRunner<'_>, + next_id: &mut u64, + ca_queue: &mut VecDeque, + pending_roa_dispatch: &mut VecDeque, + inflight_publication_points: &mut HashMap, + pending_finalization: &mut VecDeque, + finished: &mut Vec, + staging_inflight: &mut usize, + batch_metrics: &mut ReadyStageBatchMetrics, + pool_metrics: &mut StageDrainMetrics, + config: &TreeRunConfig, +) -> Result<(), TreeRunError> { + let started = Instant::now(); + loop { + let Some(result) = stage_pool + .recv_result_timeout(Duration::from_millis(0)) + .map_err(TreeRunError::Runner)? + else { + break; + }; + pool_metrics.results_drained += 1; + pool_metrics.queue_wait_ms_total += result.queue_wait_ms; + pool_metrics.queue_wait_ms_max = pool_metrics.queue_wait_ms_max.max(result.queue_wait_ms); + pool_metrics.worker_ms_total += result.worker_ms; + pool_metrics.worker_ms_max = pool_metrics.worker_ms_max.max(result.worker_ms); + *staging_inflight = staging_inflight.saturating_sub(1); + let metrics = apply_ready_publication_point_stage( + runner, + next_id, + ca_queue, + pending_roa_dispatch, + inflight_publication_points, + pending_finalization, + finished, + result.outcome, + result.metrics, + config, + config.compact_audit, + ); + batch_metrics.record(metrics); + } + pool_metrics.duration_ms += elapsed_ms(started); + Ok(()) +} + +fn submit_pending_finalization( + finalize_task_tx: &SyncSender, + pending_finalization: &mut VecDeque, + finalize_inflight: &mut usize, +) -> Result { + let started = Instant::now(); + let mut metrics = FinalizeSubmitMetrics::default(); + while let Some(task) = pending_finalization.pop_front() { + match finalize_task_tx.try_send(task) { + Ok(()) => { + metrics.submitted += 1; + *finalize_inflight += 1; + } + Err(TrySendError::Full(task)) => { + pending_finalization.push_front(task); + metrics.queue_full = true; + break; + } + Err(TrySendError::Disconnected(_task)) => { + return Err(TreeRunError::Runner( + "phase2 finalize worker queue disconnected".to_string(), + )); + } + } + } + metrics.duration_ms = elapsed_ms(started); + Ok(metrics) +} + +fn submit_pending_finalization_with_progress( + finalize_task_tx: &SyncSender, + pending_finalization: &mut VecDeque, + finalize_inflight: &mut usize, + finalize_queue_capacity: usize, + pending_roa_dispatch_len: usize, + inflight_publication_points_len: usize, +) -> Result<(), TreeRunError> { + let submit_metrics = + submit_pending_finalization(finalize_task_tx, pending_finalization, finalize_inflight)?; + if submit_metrics.submitted > 0 || submit_metrics.queue_full { + crate::logging::progress::emit!( + "phase2_finalize_task_submit", + serde_json::json!({ + "submitted": submit_metrics.submitted, + "queue_full": submit_metrics.queue_full, + "duration_ms": submit_metrics.duration_ms, + "finalize_queue_capacity": finalize_queue_capacity, + "pending_finalization_len": pending_finalization.len(), + "finalize_inflight": *finalize_inflight, + "pending_roa_dispatch_len": pending_roa_dispatch_len, + "inflight_publication_points": inflight_publication_points_len, + }), + ); + } + Ok(()) +} + +fn drain_finalize_results( + finalize_result_rx: &Receiver, + finished: &mut Vec, + finalize_inflight: &mut usize, +) -> Result { + let started = Instant::now(); + let mut metrics = FinalizeResultsDrainMetrics::default(); + loop { + match finalize_result_rx.try_recv() { + Ok(result) => { + metrics.results_drained += 1; + metrics.reduce_ms_total += result.metrics.reduce_ms; + metrics.reduce_ms_max = metrics.reduce_ms_max.max(result.metrics.reduce_ms); + metrics.finalize_ms_total += result.metrics.finalize_ms; + metrics.finalize_ms_max = metrics.finalize_ms_max.max(result.metrics.finalize_ms); + metrics.finalize_queue_wait_ms_max = metrics + .finalize_queue_wait_ms_max + .max(result.metrics.finalize_queue_wait_ms.unwrap_or(0)); + metrics.finalize_worker_ms_total += result.metrics.finalize_worker_ms; + metrics.finalize_worker_ms_max = metrics + .finalize_worker_ms_max + .max(result.metrics.finalize_worker_ms); + metrics.snapshot_pack_ms_total += result.metrics.snapshot_pack_ms; + metrics.snapshot_pack_ms_max = metrics + .snapshot_pack_ms_max + .max(result.metrics.snapshot_pack_ms); + metrics.ccr_projection_build_ms_total += result.metrics.ccr_projection_build_ms; + metrics.ccr_projection_build_ms_max = metrics + .ccr_projection_build_ms_max + .max(result.metrics.ccr_projection_build_ms); + metrics.ccr_append_ms_total += result.metrics.ccr_append_ms; + metrics.ccr_append_ms_max = + metrics.ccr_append_ms_max.max(result.metrics.ccr_append_ms); + metrics.audit_build_ms_total += result.metrics.audit_build_ms; + metrics.audit_build_ms_max = metrics + .audit_build_ms_max + .max(result.metrics.audit_build_ms); + *finalize_inflight = finalize_inflight.saturating_sub(1); + finished.push(result.finished); + } + Err(TryRecvError::Empty) => break, + Err(TryRecvError::Disconnected) => { + if *finalize_inflight == 0 { + break; + } + return Err(TreeRunError::Runner( + "phase2 finalize result channel disconnected".to_string(), + )); + } + } + } + metrics.duration_ms = elapsed_ms(started); + Ok(metrics) +} + +fn drain_finalize_results_with_progress( + finalize_result_rx: &Receiver, + finished: &mut Vec, + finalize_inflight: &mut usize, + pending_finalization_len: usize, + pending_roa_dispatch_len: usize, + inflight_publication_points_len: usize, +) -> Result<(), TreeRunError> { + let drain_metrics = drain_finalize_results(finalize_result_rx, finished, finalize_inflight)?; + if drain_metrics.results_drained >= 64 + || (drain_metrics.results_drained > 0 + && (*finalize_inflight == 0 || pending_finalization_len > 0)) + { + crate::logging::progress::emit!( + "phase2_finalize_results_drain", + serde_json::json!({ + "results_drained": drain_metrics.results_drained, + "reduce_ms_total": drain_metrics.reduce_ms_total, + "reduce_ms_max": drain_metrics.reduce_ms_max, + "finalize_ms_total": drain_metrics.finalize_ms_total, + "finalize_ms_max": drain_metrics.finalize_ms_max, + "finalize_queue_wait_ms_max": drain_metrics.finalize_queue_wait_ms_max, + "finalize_worker_ms_total": drain_metrics.finalize_worker_ms_total, + "finalize_worker_ms_max": drain_metrics.finalize_worker_ms_max, + "snapshot_pack_ms_total": drain_metrics.snapshot_pack_ms_total, + "snapshot_pack_ms_max": drain_metrics.snapshot_pack_ms_max, + "ccr_projection_build_ms_total": drain_metrics.ccr_projection_build_ms_total, + "ccr_projection_build_ms_max": drain_metrics.ccr_projection_build_ms_max, + "ccr_append_ms_total": drain_metrics.ccr_append_ms_total, + "ccr_append_ms_max": drain_metrics.ccr_append_ms_max, + "audit_build_ms_total": drain_metrics.audit_build_ms_total, + "audit_build_ms_max": drain_metrics.audit_build_ms_max, + "duration_ms": drain_metrics.duration_ms, + "pending_finalization_len": pending_finalization_len, + "finalize_inflight": *finalize_inflight, + "pending_roa_dispatch_len": pending_roa_dispatch_len, + "inflight_publication_points": inflight_publication_points_len, + }), + ); + } + Ok(()) +} + +fn run_finalize_worker( + runner: &Rpkiv1PublicationPointRunner<'_>, + finalize_task_rx: Receiver, + finalize_result_tx: mpsc::Sender, + compact_audit: bool, +) -> Result<(), TreeRunError> { + while let Ok(task) = finalize_task_rx.recv() { + let result = finalize_publication_point_state(runner, task.state, compact_audit); + if finalize_result_tx.send(result).is_err() { + return Err(TreeRunError::Runner( + "phase2 finalize result receiver disconnected".to_string(), + )); + } + } + Ok(()) +} diff --git a/src/validation/tree_parallel/finalize.rs b/src/validation/tree_parallel/finalize.rs new file mode 100644 index 0000000..335412c --- /dev/null +++ b/src/validation/tree_parallel/finalize.rs @@ -0,0 +1,488 @@ +fn finalize_publication_point_state( + runner: &Rpkiv1PublicationPointRunner<'_>, + state: InflightPublicationPoint, + compact_audit: bool, +) -> FinalizeWorkerResult { + let finalize_worker_started = Instant::now(); + let InflightPublicationPoint { + node, + fresh_stage, + objects_prepare, + repo_outcome, + warnings, + started_at, + objects_started_at, + task_count, + tasks_submitted, + first_task_submitted_at, + last_task_submitted_at, + first_result_at, + last_result_at, + worker_ms_total, + worker_ms_max, + queue_wait_ms_total, + queue_wait_ms_max, + finalize_enqueued_at, + results, + } = state; + let finalize_queue_wait_ms = finalize_enqueued_at.or(last_result_at).map(|ready_at| { + Instant::now() + .saturating_duration_since(ready_at) + .as_millis() as u64 + }); + let objects_processing_ms = objects_started_at.elapsed().as_millis() as u64; + runner.record_publication_point_step_ms( + &node.handle.manifest_rsync_uri, + "fresh_objects_processing_lifetime", + objects_processing_ms, + ); + + let (result, mut metrics, reduce_ms) = match objects_prepare { + ParallelObjectsPrepare::Staged(objects_stage) => { + let reduce_started = Instant::now(); + let locked_files = objects_stage.locked_file_count(); + let reduce_result = + reduce_parallel_roa_stage(objects_stage, results, runner.timing.as_ref()); + let reduce_ms = elapsed_ms(reduce_started); + runner.record_publication_point_step_ms( + &node.handle.manifest_rsync_uri, + "fresh_roa_reduce", + reduce_ms, + ); + + let (result, metrics) = match reduce_result { + Ok(mut objects) => { + let finalize_started = Instant::now(); + objects + .router_keys + .extend(fresh_stage.discovered_router_keys.clone()); + let finalized = runner.finalize_fresh_publication_point_from_reducer( + &node.handle, + &fresh_stage.fresh_point, + warnings, + objects, + fresh_stage.child_audits, + fresh_stage.discovered_children, + repo_outcome.repo_sync_source.as_deref(), + repo_outcome.repo_sync_phase.as_deref(), + repo_outcome.repo_sync_duration_ms, + repo_outcome.repo_sync_err.as_deref(), + ); + let finalize_ms = elapsed_ms(finalize_started); + match finalized { + Ok(output) => { + let metrics = finalize_metrics_from_output( + &output, + reduce_ms, + finalize_ms, + finalize_queue_wait_ms, + 0, + locked_files, + ); + ( + compact_phase2_finished_result(output.result, compact_audit), + metrics, + ) + } + Err(err) => ( + FinishedPublicationPointResult::Err(err), + FinalizePublicationPointMetrics { + reduce_ms, + finalize_ms, + finalize_queue_wait_ms, + locked_files, + ..FinalizePublicationPointMetrics::default() + }, + ), + } + } + Err(err) => ( + FinishedPublicationPointResult::Err(err), + FinalizePublicationPointMetrics { + reduce_ms, + finalize_queue_wait_ms, + locked_files, + ..FinalizePublicationPointMetrics::default() + }, + ), + }; + (result, metrics, reduce_ms) + } + ParallelObjectsPrepare::Complete(objects) => { + // ROA prepare already produced complete objects for this publication + // point, so there is nothing to reduce; finalize directly. This is + // the former control-thread "direct finalize", now running on the + // finalize worker through the regular task queue. + let locked_files = fresh_stage.fresh_point.files().len(); + let finalize_started = Instant::now(); + let finalized = runner.finalize_fresh_publication_point_from_reducer( + &node.handle, + &fresh_stage.fresh_point, + warnings, + objects, + fresh_stage.child_audits, + fresh_stage.discovered_children, + repo_outcome.repo_sync_source.as_deref(), + repo_outcome.repo_sync_phase.as_deref(), + repo_outcome.repo_sync_duration_ms, + repo_outcome.repo_sync_err.as_deref(), + ); + let finalize_ms = elapsed_ms(finalize_started); + let (result, metrics) = match finalized { + Ok(output) => { + let metrics = finalize_metrics_from_output( + &output, + 0, + finalize_ms, + finalize_queue_wait_ms, + 0, + locked_files, + ); + ( + compact_phase2_finished_result(output.result, compact_audit), + metrics, + ) + } + Err(err) => ( + FinishedPublicationPointResult::Err(err), + FinalizePublicationPointMetrics { + finalize_ms, + finalize_queue_wait_ms, + locked_files, + ..FinalizePublicationPointMetrics::default() + }, + ), + }; + (result, metrics, 0) + } + }; + let finalize_worker_ms = elapsed_ms(finalize_worker_started); + metrics.finalize_worker_ms = finalize_worker_ms; + runner.record_publication_point_step_ms( + &node.handle.manifest_rsync_uri, + "fresh_finalize_worker", + finalize_worker_ms, + ); + runner.record_publication_point_step_ms( + &node.handle.manifest_rsync_uri, + "fresh_finalize_queue_wait", + finalize_queue_wait_ms.unwrap_or(0), + ); + runner.record_publication_point_step_ms( + &node.handle.manifest_rsync_uri, + "fresh_finalize", + metrics.finalize_ms, + ); + runner.record_publication_point_total_ms( + &node.handle.manifest_rsync_uri, + started_at.elapsed().as_millis() as u64, + ); + emit_finalize_breakdown( + "phase2_finalize_worker_breakdown", + node.handle.manifest_rsync_uri.as_str(), + node.handle.publication_point_rsync_uri.as_str(), + &metrics, + ); + crate::logging::progress::emit!( + "phase2_publication_point_reduced", + serde_json::json!({ + "manifest_rsync_uri": node.handle.manifest_rsync_uri.as_str(), + "publication_point_rsync_uri": node.handle.publication_point_rsync_uri.as_str(), + "objects_processing_ms": objects_processing_ms, + "task_count": task_count, + "tasks_submitted": tasks_submitted, + "first_task_submitted_ms": first_task_submitted_at.map(|t| t.saturating_duration_since(objects_started_at).as_millis() as u64), + "last_task_submitted_ms": last_task_submitted_at.map(|t| t.saturating_duration_since(objects_started_at).as_millis() as u64), + "task_submit_span_ms": match (first_task_submitted_at, last_task_submitted_at) { + (Some(first), Some(last)) => Some(last.saturating_duration_since(first).as_millis() as u64), + _ => None, + }, + "first_result_ms": first_result_at.map(|t| t.saturating_duration_since(objects_started_at).as_millis() as u64), + "last_result_ms": last_result_at.map(|t| t.saturating_duration_since(objects_started_at).as_millis() as u64), + "all_results_ready_ms": last_result_at.map(|t| t.saturating_duration_since(objects_started_at).as_millis() as u64), + "finalize_queue_wait_ms": finalize_queue_wait_ms, + "result_span_ms": match (first_result_at, last_result_at) { + (Some(first), Some(last)) => Some(last.saturating_duration_since(first).as_millis() as u64), + _ => None, + }, + "worker_ms_total": worker_ms_total, + "worker_ms_max": worker_ms_max, + "worker_ms_avg": if task_count > 0 { worker_ms_total / task_count as u64 } else { 0 }, + "queue_wait_ms_total": queue_wait_ms_total, + "queue_wait_ms_max": queue_wait_ms_max, + "queue_wait_ms_avg": if task_count > 0 { queue_wait_ms_total / task_count as u64 } else { 0 }, + "reduce_ms": reduce_ms, + "finalize_ms": metrics.finalize_ms, + "finalize_worker_ms": finalize_worker_ms, + "snapshot_pack_ms": metrics.snapshot_pack_ms, + "ccr_projection_build_ms": metrics.ccr_projection_build_ms, + "ccr_append_ms": metrics.ccr_append_ms, + "audit_build_ms": metrics.audit_build_ms, + "locked_files": metrics.locked_files, + "child_count": metrics.child_count, + "warning_count": metrics.warning_count, + "vrp_count": metrics.vrp_count, + "vap_count": metrics.vap_count, + "router_key_count": metrics.router_key_count, + "audit_object_count": metrics.audit_object_count, + "total_duration_ms": started_at.elapsed().as_millis() as u64, + }), + ); + FinalizeWorkerResult { + finished: FinishedPublicationPoint { + node: FinishedPublicationPointNode::from_queued(node), + result, + }, + metrics, + } +} + +fn drain_repo_events( + repo_runtime: &dyn crate::scheduler::repo_runtime::RepoSyncRuntime, + ca_waiting_repo_by_identity: &mut HashMap>, + ready_queue: &mut VecDeque, + timeout: Duration, +) -> Result { + let started = Instant::now(); + let mut metrics = RepoDrainMetrics::default(); + let events = repo_runtime + .drain_repo_results_timeout(timeout, REPO_RESULT_DRAIN_MAX_EVENTS) + .map_err(TreeRunError::Runner)?; + for event in events { + metrics.event_count += 1; + metrics.completions += event.completions.len(); + for completion in event.completions { + let mut outcome = completion.outcome; + if completion.identity != event.transport_identity { + // Shared RRDP/rsync transports release many publication points, but the transport + // wall time should only be counted once in per-PP stage timing aggregation. + outcome.repo_sync_duration_ms = 0; + } + if let Some(waiters) = ca_waiting_repo_by_identity.remove(&completion.identity) { + metrics.ready_enqueued += waiters.len(); + for node in waiters { + ready_queue.push_back(ReadyCaInstance { + node, + repo_outcome: outcome.clone(), + ready_enqueued_at: Instant::now(), + }); + } + } + } + } + metrics.duration_ms = elapsed_ms(started); + Ok(metrics) +} + +fn event_poll_timeout( + ca_queue: &VecDeque, + ready_queue: &VecDeque, + pending_roa_dispatch: &VecDeque, + inflight_publication_points: &HashMap, + pending_finalization: &VecDeque, + finalize_inflight: usize, + staging_inflight: usize, + instances_started: usize, + config: &TreeRunConfig, +) -> Duration { + // Stage results pending collection must not let the loop sleep: with the + // stage pool enabled a 50ms nap per turn would collapse throughput. + if !ready_queue.is_empty() + || !pending_roa_dispatch.is_empty() + || !inflight_publication_points.is_empty() + || !pending_finalization.is_empty() + || staging_inflight > 0 + || (!ca_queue.is_empty() && can_start_more(instances_started, config)) + { + Duration::from_millis(0) + } else if finalize_inflight > 0 { + Duration::from_millis(10) + } else { + Duration::from_millis(50) + } +} + +fn is_complete( + ca_queue: &VecDeque, + ready_queue: &VecDeque, + ca_waiting_repo_by_identity: &HashMap>, + pending_roa_dispatch: &VecDeque, + inflight_publication_points: &HashMap, + pending_finalization: &VecDeque, + finalize_inflight: usize, + staging_inflight: usize, + instances_started: usize, + config: &TreeRunConfig, +) -> bool { + // `staging_inflight == 0` additionally implies the stage result channel is + // drained empty: the drain loop collects every available result each turn + // and only decrements the counter while collecting. + let ca_queue_done = ca_queue.is_empty() || !can_start_more(instances_started, config); + ca_queue_done + && ready_queue.is_empty() + && ca_waiting_repo_by_identity.is_empty() + && pending_roa_dispatch.is_empty() + && inflight_publication_points.is_empty() + && pending_finalization.is_empty() + && finalize_inflight == 0 + && staging_inflight == 0 +} + +/// Minimum publication points per reduction shard; smaller runs stay +/// single-threaded to avoid thread-spawn overhead dominating the reduction. +const TREE_OUTPUT_MIN_SHARD_LEN: usize = 4096; +/// Hard cap on reduction shards regardless of core count. +const TREE_OUTPUT_MAX_SHARDS: usize = 8; + +fn tree_output_shard_count(len: usize) -> usize { + if len < TREE_OUTPUT_MIN_SHARD_LEN * 2 { + return 1; + } + let parallel = std::thread::available_parallelism() + .map(|n| n.get()) + .unwrap_or(1); + parallel + .min(TREE_OUTPUT_MAX_SHARDS) + .min(len / TREE_OUTPUT_MIN_SHARD_LEN) + .max(1) +} + +/// Per-shard reduction result of the phase2 output merge. Shards cover +/// contiguous ranges of the id-sorted finished list, so concatenating shard +/// outputs in shard order reproduces the sequential single-threaded order +/// exactly. +#[derive(Default)] +struct TreeOutputShardReduction { + instances_processed: usize, + instances_failed: usize, + warnings: Vec, + vrps: Vec, + aspas: Vec, + router_keys: Vec, + publication_points: Vec, +} + +fn reduce_finished_shard(items: Vec) -> TreeOutputShardReduction { + let mut reduction = TreeOutputShardReduction::default(); + for item in items { + match item.result { + FinishedPublicationPointResult::Ok { + warnings: result_warnings, + objects, + audit, + } => { + reduction.instances_processed += 1; + reduction.warnings.extend(result_warnings); + reduction.warnings.extend(objects.warnings); + reduction.vrps.extend(objects.vrps); + reduction.aspas.extend(objects.aspas); + reduction.router_keys.extend(objects.router_keys); + + let mut audit: PublicationPointAudit = audit; + audit.node_id = Some(item.node.id); + audit.parent_node_id = item.node.parent_id; + audit.discovered_from = item.node.discovered_from; + reduction.publication_points.push(audit); + } + FinishedPublicationPointResult::Err(err) => { + reduction.instances_failed += 1; + reduction.warnings.push( + Warning::new(format!("publication point failed: {err}")) + .with_context(&item.node.manifest_rsync_uri), + ); + } + } + } + reduction +} + +fn merge_shard_reductions(reductions: Vec) -> TreeOutputShardReduction { + let mut iter = reductions.into_iter(); + let mut merged = iter.next().unwrap_or_default(); + for mut other in iter { + merged.instances_processed += other.instances_processed; + merged.instances_failed += other.instances_failed; + merged.warnings.append(&mut other.warnings); + merged.vrps.append(&mut other.vrps); + merged.aspas.append(&mut other.aspas); + merged.router_keys.append(&mut other.router_keys); + merged + .publication_points + .append(&mut other.publication_points); + } + merged +} + +fn build_tree_output(mut finished: Vec) -> TreeRunAuditOutput { + let total_started = Instant::now(); + finished.sort_by_key(|item| item.node.id); + let sort_ms = total_started.elapsed().as_millis() as u64; + let shard_count = tree_output_shard_count(finished.len()); + + let reduce_started = Instant::now(); + let reductions = if shard_count <= 1 { + vec![reduce_finished_shard(finished)] + } else { + let chunk_len = finished.len().div_ceil(shard_count); + let mut shards: Vec> = Vec::new(); + let mut rest = finished; + while !rest.is_empty() { + let split_at = chunk_len.min(rest.len()); + let tail = rest.split_off(split_at); + shards.push(std::mem::replace(&mut rest, tail)); + } + std::thread::scope(|scope| { + let handles: Vec<_> = shards + .into_iter() + .map(|shard| scope.spawn(move || reduce_finished_shard(shard))) + .collect(); + handles + .into_iter() + .map(|handle| handle.join().expect("tree output reduction shard panicked")) + .collect() + }) + }; + let reduce_ms = reduce_started.elapsed().as_millis() as u64; + + let merge_started = Instant::now(); + let merged = merge_shard_reductions(reductions); + let merge_ms = merge_started.elapsed().as_millis() as u64; + + let finalize_started = Instant::now(); + let output = TreeRunAuditOutput { + tree: TreeRunOutput { + instances_processed: merged.instances_processed, + instances_failed: merged.instances_failed, + warnings: merged.warnings, + vrps: merged.vrps, + aspas: merged.aspas, + router_keys: merged.router_keys, + }, + publication_points: merged.publication_points, + }; + let finalize_ms = finalize_started.elapsed().as_millis() as u64; + + crate::logging::progress::emit!( + "phase2_build_tree_output", + serde_json::json!({ + "sort_ms": sort_ms, + "shard_count": shard_count, + "reduce_ms": reduce_ms, + "merge_ms": merge_ms, + "finalize_ms": finalize_ms, + "total_ms": total_started.elapsed().as_millis() as u64, + "publication_points": output.publication_points.len(), + "instances_processed": output.tree.instances_processed, + "instances_failed": output.tree.instances_failed, + }), + ); + output +} + +pub fn run_tree_parallel_phase2_audit( + root: CaInstanceHandle, + runner: &Rpkiv1PublicationPointRunner<'_>, + config: &TreeRunConfig, +) -> Result { + run_tree_parallel_phase2_audit_multi_root(vec![root], runner, config) +} diff --git a/src/validation/tree_parallel/phase2.rs b/src/validation/tree_parallel/phase2.rs new file mode 100644 index 0000000..8b854b7 --- /dev/null +++ b/src/validation/tree_parallel/phase2.rs @@ -0,0 +1,445 @@ +pub fn run_tree_parallel_phase2_audit_multi_root( + roots: Vec, + runner: &Rpkiv1PublicationPointRunner<'_>, + config: &TreeRunConfig, +) -> Result { + if runner.policy.signed_object_failure_policy == SignedObjectFailurePolicy::DropPublicationPoint + { + return run_tree_serial_audit_multi_root(roots, runner, config); + } + + let Some(repo_runtime) = runner.repo_sync_runtime.as_ref() else { + return run_tree_serial_audit_multi_root(roots, runner, config); + }; + if runner.parallel_roa_worker_pool.is_none() { + return run_tree_serial_audit_multi_root(roots, runner, config); + } + + let mut next_id: u64 = 0; + let mut ca_queue: VecDeque = VecDeque::new(); + for root in roots { + ca_queue.push_back(QueuedCaInstance { + id: next_id, + handle: root, + parent_id: None, + discovered_from: None, + }); + next_id += 1; + } + + let mut visited_manifest_uris: HashSet = HashSet::new(); + let mut ca_waiting_repo_by_identity: HashMap> = + HashMap::new(); + let mut ready_queue: VecDeque = VecDeque::new(); + let mut inflight_publication_points: HashMap = HashMap::new(); + let mut pending_finalization: VecDeque = VecDeque::new(); + let mut pending_roa_dispatch: VecDeque = VecDeque::new(); + let mut finished: Vec = Vec::new(); + let mut instances_started = 0usize; + let phase2_config = runner.parallel_phase2_config.as_ref(); + let ready_batch_size = phase2_config + .map(|cfg| cfg.ready_batch_size) + .unwrap_or(256) + .max(1); + let ready_batch_wall_time_budget_ms = phase2_config + .map(|cfg| cfg.ready_batch_wall_time_budget_ms) + .unwrap_or(100) + .max(1); + let ready_batch_wall_time_budget = Duration::from_millis(ready_batch_wall_time_budget_ms); + let object_result_drain_batch_size = phase2_config + .map(|cfg| cfg.object_result_drain_batch_size) + .unwrap_or(2048) + .max(1); + let publication_point_finalize_queue_capacity = phase2_config + .map(|cfg| cfg.publication_point_finalize_queue_capacity) + .unwrap_or(32768) + .max(1); + // Experimental ready-stage pool: `stage_workers == 0` keeps the inline + // compute+apply staging path byte-for-byte; any positive value moves the + // compute phase to a scoped worker pool. + let stage_worker_count = phase2_config.map(|cfg| cfg.stage_workers).unwrap_or(0); + let stage_queue_capacity = phase2_config + .map(|cfg| cfg.worker_queue_capacity) + .unwrap_or(256) + .max(1); + + let (finalize_task_tx, finalize_task_rx) = + mpsc::sync_channel::(publication_point_finalize_queue_capacity); + let (finalize_result_tx, finalize_result_rx) = mpsc::channel::(); + let mut finalize_inflight = 0usize; + + std::thread::scope(|scope| { + let finalize_worker = scope.spawn(move || { + run_finalize_worker( + runner, + finalize_task_rx, + finalize_result_tx, + config.compact_audit, + ) + }); + + // The stage pool borrows the runner like the finalize worker does; it + // lives inside this scope and is dropped before the scope joins. + let mut stage_pool = if stage_worker_count > 0 { + Some( + ReadyStagePool::new( + scope, + stage_worker_count, + stage_queue_capacity, + ReadyStageTaskExecutor { runner }, + ) + .map_err(TreeRunError::Runner)?, + ) + } else { + None + }; + // Submitted-but-not-yet-drained stage tasks. The drain loop collects + // every available result each turn, so `staging_inflight == 0` also + // implies the stage result channel is empty. + let mut staging_inflight = 0usize; + + let run_result: Result<(), TreeRunError> = (|| { + loop { + let control_loop_started = Instant::now(); + // With the stage pool enabled the batch wall clock covers the + // whole turn (turn-head drain + dispatch + apply); the inline + // path keeps its historical start point at the ready batch. + let turn_stage_started = Instant::now(); + let mut ready_batch_metrics = ReadyStageBatchMetrics::default(); + let mut stage_drain_metrics = StageDrainMetrics::default(); + drain_finalize_results_with_progress( + &finalize_result_rx, + &mut finished, + &mut finalize_inflight, + pending_finalization.len(), + pending_roa_dispatch.len(), + inflight_publication_points.len(), + )?; + flush_pending_roa_dispatch_with_progress( + runner, + &mut pending_roa_dispatch, + &mut inflight_publication_points, + &pending_finalization, + )?; + drain_object_results_with_progress( + runner, + &mut inflight_publication_points, + &mut pending_finalization, + pending_roa_dispatch.len(), + object_result_drain_batch_size, + )?; + if let Some(pool) = stage_pool.as_ref() { + drain_stage_results( + pool, + runner, + &mut next_id, + &mut ca_queue, + &mut pending_roa_dispatch, + &mut inflight_publication_points, + &mut pending_finalization, + &mut finished, + &mut staging_inflight, + &mut ready_batch_metrics, + &mut stage_drain_metrics, + config, + )?; + } + submit_pending_finalization_with_progress( + &finalize_task_tx, + &mut pending_finalization, + &mut finalize_inflight, + publication_point_finalize_queue_capacity, + pending_roa_dispatch.len(), + inflight_publication_points.len(), + )?; + + start_queued_ca_instances( + repo_runtime.as_ref(), + &mut ca_queue, + &mut ready_queue, + &mut ca_waiting_repo_by_identity, + &mut finished, + &mut visited_manifest_uris, + &mut instances_started, + config, + ); + + let repo_poll_timeout = event_poll_timeout( + &ca_queue, + &ready_queue, + &pending_roa_dispatch, + &inflight_publication_points, + &pending_finalization, + finalize_inflight, + staging_inflight, + instances_started, + config, + ); + let repo_metrics = drain_repo_events( + repo_runtime.as_ref(), + &mut ca_waiting_repo_by_identity, + &mut ready_queue, + repo_poll_timeout, + )?; + if repo_metrics.event_count > 0 { + crate::logging::progress::emit!( + "phase2_repo_events_drain", + serde_json::json!({ + "event_count": repo_metrics.event_count, + "completions": repo_metrics.completions, + "ready_enqueued": repo_metrics.ready_enqueued, + "duration_ms": repo_metrics.duration_ms, + "ready_queue_len": ready_queue.len(), + "ca_waiting_repo_identities": ca_waiting_repo_by_identity.len(), + }), + ); + } + + let ready_batch_started = Instant::now(); + let mut ready_time_budget_exhausted = false; + let mut stage_dispatch_metrics = StageDispatchMetrics::default(); + if let Some(pool) = stage_pool.as_mut() { + // Pool path: the ready batch becomes a dispatch loop that + // submits compute tasks and applies whatever results are + // already available; backpressure requeues for next turn. + stage_dispatch_metrics = submit_ready_batch_to_stage_pool( + pool, + &mut ready_queue, + &mut staging_inflight, + ready_batch_size, + ready_batch_wall_time_budget, + )?; + ready_time_budget_exhausted = stage_dispatch_metrics.queue_full + || (!ready_queue.is_empty() + && ready_batch_started.elapsed() >= ready_batch_wall_time_budget); + drain_stage_results( + pool, + runner, + &mut next_id, + &mut ca_queue, + &mut pending_roa_dispatch, + &mut inflight_publication_points, + &mut pending_finalization, + &mut finished, + &mut staging_inflight, + &mut ready_batch_metrics, + &mut stage_drain_metrics, + config, + )?; + } else { + while ready_batch_metrics.ready_count < ready_batch_size { + let Some(ready) = ready_queue.pop_front() else { + break; + }; + let ready_queue_len_after_pop = ready_queue.len(); + let (outcome, metrics) = compute_ready_publication_point_stage( + runner, + ready, + ready_queue_len_after_pop, + ); + let metrics = apply_ready_publication_point_stage( + runner, + &mut next_id, + &mut ca_queue, + &mut pending_roa_dispatch, + &mut inflight_publication_points, + &mut pending_finalization, + &mut finished, + outcome, + metrics, + config, + config.compact_audit, + ); + ready_batch_metrics.record(metrics); + if ready_batch_metrics.ready_count > 0 + && ready_batch_started.elapsed() >= ready_batch_wall_time_budget + { + ready_time_budget_exhausted = !ready_queue.is_empty(); + break; + } + } + } + if ready_batch_metrics.ready_count > 0 { + ready_batch_metrics.total_ms = if stage_pool.is_some() { + elapsed_ms(turn_stage_started) + } else { + elapsed_ms(ready_batch_started) + }; + let ready_count_budget_exhausted = ready_batch_metrics.ready_count + >= ready_batch_size + && !ready_queue.is_empty(); + ready_time_budget_exhausted = ready_time_budget_exhausted + || (!ready_queue.is_empty() + && ready_batch_metrics.total_ms >= ready_batch_wall_time_budget_ms); + emit_ready_queue_batch_progress( + &ready_batch_metrics, + ready_batch_size, + ready_batch_wall_time_budget_ms, + ready_queue.len(), + ready_count_budget_exhausted, + ready_time_budget_exhausted, + ca_queue.len(), + pending_roa_dispatch.len(), + inflight_publication_points.len(), + pending_finalization.len(), + finalize_inflight, + ); + crate::logging::progress::emit!( + "phase2_ready_queue_stage_fresh_breakdown", + serde_json::json!({ + "ready_count": ready_batch_metrics.ready_count, + "stage_fresh_ms_total": ready_batch_metrics.stage_fresh_ms_total, + "stage_fresh_ms_max": ready_batch_metrics.stage_fresh_ms_max, + "stage_fresh_ms_max_manifest_rsync_uri": ready_batch_metrics.stage_fresh_ms_max_manifest_rsync_uri, + "stage_fresh_ms_max_publication_point_rsync_uri": ready_batch_metrics.stage_fresh_ms_max_publication_point_rsync_uri, + "snapshot_prepare_ms_total": ready_batch_metrics.snapshot_prepare_ms_total, + "snapshot_prepare_ms_max": ready_batch_metrics.snapshot_prepare_ms_max, + "snapshot_current_index_lock_ms_total": ready_batch_metrics.snapshot_current_index_lock_ms_total, + "snapshot_current_index_lock_ms_max": ready_batch_metrics.snapshot_current_index_lock_ms_max, + "snapshot_manifest_load_ms_total": ready_batch_metrics.snapshot_manifest_load_ms_total, + "snapshot_manifest_load_ms_max": ready_batch_metrics.snapshot_manifest_load_ms_max, + "snapshot_manifest_index_lookup_ms_total": ready_batch_metrics.snapshot_manifest_index_lookup_ms_total, + "snapshot_manifest_index_lookup_ms_max": ready_batch_metrics.snapshot_manifest_index_lookup_ms_max, + "snapshot_manifest_blob_load_ms_total": ready_batch_metrics.snapshot_manifest_blob_load_ms_total, + "snapshot_manifest_blob_load_ms_max": ready_batch_metrics.snapshot_manifest_blob_load_ms_max, + "snapshot_manifest_decode_ms_total": ready_batch_metrics.snapshot_manifest_decode_ms_total, + "snapshot_manifest_decode_ms_max": ready_batch_metrics.snapshot_manifest_decode_ms_max, + "snapshot_anti_rollback_ms_total": ready_batch_metrics.snapshot_anti_rollback_ms_total, + "snapshot_anti_rollback_ms_max": ready_batch_metrics.snapshot_anti_rollback_ms_max, + "anti_rollback_meta_hit_count": ready_batch_metrics.anti_rollback_meta_hit_count, + "anti_rollback_meta_miss_count": ready_batch_metrics.anti_rollback_meta_miss_count, + "snapshot_manifest_entries_ms_total": ready_batch_metrics.snapshot_manifest_entries_ms_total, + "snapshot_manifest_entries_ms_max": ready_batch_metrics.snapshot_manifest_entries_ms_max, + "snapshot_pack_files_ms_total": ready_batch_metrics.snapshot_pack_files_ms_total, + "snapshot_pack_files_ms_max": ready_batch_metrics.snapshot_pack_files_ms_max, + "snapshot_pack_files_index_lookup_ms_total": ready_batch_metrics.snapshot_pack_files_index_lookup_ms_total, + "snapshot_pack_files_index_lookup_ms_max": ready_batch_metrics.snapshot_pack_files_index_lookup_ms_max, + "snapshot_pack_files_blob_load_ms_total": ready_batch_metrics.snapshot_pack_files_blob_load_ms_total, + "snapshot_pack_files_blob_load_ms_max": ready_batch_metrics.snapshot_pack_files_blob_load_ms_max, + "snapshot_ee_path_validate_ms_total": ready_batch_metrics.snapshot_ee_path_validate_ms_total, + "snapshot_ee_path_validate_ms_max": ready_batch_metrics.snapshot_ee_path_validate_ms_max, + "snapshot_manifest_file_count_total": ready_batch_metrics.snapshot_manifest_file_count_total, + "snapshot_manifest_file_count_max": ready_batch_metrics.snapshot_manifest_file_count_max, + "child_discovery_ms_total": ready_batch_metrics.child_discovery_ms_total, + "child_discovery_ms_max": ready_batch_metrics.child_discovery_ms_max, + "batch_duration_ms": ready_batch_metrics.total_ms, + }), + ); + } + if stage_pool.is_some() + && (stage_dispatch_metrics.submitted > 0 + || stage_drain_metrics.results_drained > 0 + || stage_dispatch_metrics.queue_full) + { + crate::logging::progress::emit!( + "phase2_stage_pool_stats", + serde_json::json!({ + "stage_workers": stage_worker_count, + "submitted": stage_dispatch_metrics.submitted, + "results_drained": stage_drain_metrics.results_drained, + "queue_full": stage_dispatch_metrics.queue_full, + "staging_inflight": staging_inflight, + "ready_queue_len": ready_queue.len(), + "queue_wait_ms_total": stage_drain_metrics.queue_wait_ms_total, + "queue_wait_ms_max": stage_drain_metrics.queue_wait_ms_max, + "worker_ms_total": stage_drain_metrics.worker_ms_total, + "worker_ms_max": stage_drain_metrics.worker_ms_max, + "dispatch_duration_ms": stage_dispatch_metrics.duration_ms, + "drain_duration_ms": stage_drain_metrics.duration_ms, + }), + ); + } + + flush_pending_roa_dispatch_with_progress( + runner, + &mut pending_roa_dispatch, + &mut inflight_publication_points, + &pending_finalization, + )?; + drain_object_results_with_progress( + runner, + &mut inflight_publication_points, + &mut pending_finalization, + pending_roa_dispatch.len(), + object_result_drain_batch_size, + )?; + submit_pending_finalization_with_progress( + &finalize_task_tx, + &mut pending_finalization, + &mut finalize_inflight, + publication_point_finalize_queue_capacity, + pending_roa_dispatch.len(), + inflight_publication_points.len(), + )?; + drain_finalize_results_with_progress( + &finalize_result_rx, + &mut finished, + &mut finalize_inflight, + pending_finalization.len(), + pending_roa_dispatch.len(), + inflight_publication_points.len(), + )?; + + emit_control_loop_slow( + elapsed_ms(control_loop_started), + repo_poll_timeout, + &repo_metrics, + &ready_batch_metrics, + ca_queue.len(), + ready_queue.len(), + ca_waiting_repo_by_identity.len(), + pending_roa_dispatch.len(), + inflight_publication_points.len(), + pending_finalization.len(), + finalize_inflight, + ); + + if is_complete( + &ca_queue, + &ready_queue, + &ca_waiting_repo_by_identity, + &pending_roa_dispatch, + &inflight_publication_points, + &pending_finalization, + finalize_inflight, + staging_inflight, + instances_started, + config, + ) { + break; + } + } + + repo_runtime + .reset_run_state() + .map_err(TreeRunError::Runner)?; + Ok(()) + })(); + + drop(finalize_task_tx); + // Dropping the pool closes the stage task queues so the scoped stage + // workers exit before the scope joins them below. + drop(stage_pool); + let worker_result = finalize_worker + .join() + .map_err(|_| TreeRunError::Runner("phase2 finalize worker panicked".to_string()))?; + run_result?; + worker_result?; + drain_finalize_results_with_progress( + &finalize_result_rx, + &mut finished, + &mut finalize_inflight, + pending_finalization.len(), + pending_roa_dispatch.len(), + inflight_publication_points.len(), + )?; + if finalize_inflight != 0 || !pending_finalization.is_empty() { + return Err(TreeRunError::Runner(format!( + "phase2 finalize worker stopped with pending work: queued={} inflight={}", + pending_finalization.len(), + finalize_inflight + ))); + } + Ok(build_tree_output(finished)) + }) +} diff --git a/src/validation/tree_parallel/ready_stage.rs b/src/validation/tree_parallel/ready_stage.rs new file mode 100644 index 0000000..dc4fb12 --- /dev/null +++ b/src/validation/tree_parallel/ready_stage.rs @@ -0,0 +1,728 @@ +fn emit_ready_queue_batch_progress( + metrics: &ReadyStageBatchMetrics, + ready_batch_size: usize, + ready_batch_wall_time_budget_ms: u64, + ready_queue_len_after_batch: usize, + ready_count_budget_exhausted: bool, + ready_time_budget_exhausted: bool, + ca_queue_len_after_batch: usize, + pending_roa_dispatch_len_after_batch: usize, + inflight_publication_points_after_batch: usize, + pending_finalization_len_after_batch: usize, + finalize_inflight_after_batch: usize, +) { + crate::logging::progress::emit!( + "phase2_ready_queue_batch", + serde_json::json!({ + "ready_count": metrics.ready_count, + "fallback_count": metrics.fallback_count, + "complete_count": metrics.complete_count, + "staged_count": metrics.staged_count, + "zero_task_count": metrics.zero_task_count, + "error_count": metrics.error_count, + "discovered_children": metrics.discovered_children, + "locked_files": metrics.locked_files, + "roa_tasks": metrics.roa_tasks, + "aspa_objects": metrics.aspa_objects, + "stage_fresh_ms_total": metrics.stage_fresh_ms_total, + "stage_fresh_ms_max": metrics.stage_fresh_ms_max, + "stage_fresh_ms_max_manifest_rsync_uri": metrics.stage_fresh_ms_max_manifest_rsync_uri, + "stage_fresh_ms_max_publication_point_rsync_uri": metrics.stage_fresh_ms_max_publication_point_rsync_uri, + "prepare_ms_total": metrics.prepare_ms_total, + "prepare_ms_max": metrics.prepare_ms_max, + "build_roa_tasks_ms_total": metrics.build_roa_tasks_ms_total, + "build_roa_tasks_ms_max": metrics.build_roa_tasks_ms_max, + "batch_duration_ms": metrics.total_ms, + "ready_batch_size": ready_batch_size, + "ready_batch_wall_time_budget_ms": ready_batch_wall_time_budget_ms, + "ready_queue_len_after_batch": ready_queue_len_after_batch, + "ready_queue_budget_exhausted": ready_queue_len_after_batch > 0, + "ready_count_budget_exhausted": ready_count_budget_exhausted, + "ready_time_budget_exhausted": ready_time_budget_exhausted, + "ca_queue_len_after_batch": ca_queue_len_after_batch, + "pending_roa_dispatch_len_after_batch": pending_roa_dispatch_len_after_batch, + "inflight_publication_points_after_batch": inflight_publication_points_after_batch, + "pending_finalization_len_after_batch": pending_finalization_len_after_batch, + "finalize_inflight_after_batch": finalize_inflight_after_batch, + }), + ); + crate::logging::progress::emit!( + "phase2_ready_queue_control_breakdown", + serde_json::json!({ + "ready_count": metrics.ready_count, + "ready_queue_wait_ms_total": metrics.ready_queue_wait_ms_total, + "ready_queue_wait_ms_max": metrics.ready_queue_wait_ms_max, + "child_enqueue_ms_total": metrics.child_enqueue_ms_total, + "child_enqueue_ms_max": metrics.child_enqueue_ms_max, + "roa_presence_scan_ms_total": metrics.roa_presence_scan_ms_total, + "roa_presence_scan_ms_max": metrics.roa_presence_scan_ms_max, + "direct_finalize_ms_total": metrics.direct_finalize_ms_total, + "direct_finalize_ms_max": metrics.direct_finalize_ms_max, + "fallback_full_run_ms_total": metrics.fallback_full_run_ms_total, + "fallback_full_run_ms_max": metrics.fallback_full_run_ms_max, + "batch_duration_ms": metrics.total_ms, + }), + ); +} + +fn can_start_more(instances_started: usize, config: &TreeRunConfig) -> bool { + config + .max_instances + .map(|max| instances_started < max) + .unwrap_or(true) +} + +fn start_queued_ca_instances( + repo_runtime: &dyn crate::scheduler::repo_runtime::RepoSyncRuntime, + ca_queue: &mut VecDeque, + ready_queue: &mut VecDeque, + ca_waiting_repo_by_identity: &mut HashMap>, + finished: &mut Vec, + visited_manifest_uris: &mut HashSet, + instances_started: &mut usize, + config: &TreeRunConfig, +) { + while can_start_more(*instances_started, config) { + let Some(node) = ca_queue.pop_front() else { + break; + }; + if !visited_manifest_uris.insert(node.handle.manifest_rsync_uri.clone()) { + continue; + } + if !ca_depth_is_allowed(config, node.handle.depth) { + continue; + } + *instances_started += 1; + match repo_runtime.request_publication_point_repo(&node.handle, 0) { + Ok(RepoSyncRequestStatus::Ready { mut outcome, .. }) => { + // Ready here means this CA is reusing repo work that has already completed + // (often due to child request scheduling). Do not add the transport duration + // again. + outcome.repo_sync_duration_ms = 0; + ready_queue.push_back(ReadyCaInstance { + node, + repo_outcome: outcome, + ready_enqueued_at: Instant::now(), + }); + } + Ok(RepoSyncRequestStatus::Pending { identity, .. }) => { + ca_waiting_repo_by_identity + .entry(identity) + .or_default() + .push(node); + } + Err(err) => { + finished.push(FinishedPublicationPoint { + node: FinishedPublicationPointNode::from_queued(node), + result: FinishedPublicationPointResult::Err(err), + }); + } + } + } +} + +fn compute_ready_publication_point_stage( + runner: &Rpkiv1PublicationPointRunner<'_>, + ready: ReadyCaInstance, + ready_queue_len_after_pop: usize, +) -> (StageOutcome, ReadyStageMetrics) { + let publication_point_started = Instant::now(); + let ready_queue_wait_ms = publication_point_started + .saturating_duration_since(ready.ready_enqueued_at) + .as_millis() as u64; + let mut metrics = ReadyStageMetrics { + ready_count: 1, + manifest_rsync_uri: Some(ready.node.handle.manifest_rsync_uri.clone()), + publication_point_rsync_uri: Some(ready.node.handle.publication_point_rsync_uri.clone()), + ready_queue_wait_ms, + ready_queue_len_after_pop, + ..ReadyStageMetrics::default() + }; + let mut warnings = ready.repo_outcome.warnings.clone(); + let repo_outcome = ready.repo_outcome.clone(); + let stage_fresh_started = Instant::now(); + let stage = runner.stage_fresh_publication_point_after_repo_ready( + &ready.node.handle, + repo_outcome.repo_sync_ok, + repo_outcome.repo_sync_err.as_deref(), + ); + metrics.stage_fresh_ms = elapsed_ms(stage_fresh_started); + + let fresh_stage = match stage { + Ok(stage) => stage, + Err(err) => { + if metrics.stage_fresh_ms >= crate::logging::progress::stage_fresh_slow_threshold_ms() { + crate::logging::progress::emit!( + "phase2_stage_fresh_slow", + serde_json::json!({ + "manifest_rsync_uri": ready.node.handle.manifest_rsync_uri.as_str(), + "publication_point_rsync_uri": ready.node.handle.publication_point_rsync_uri.as_str(), + "status": "error", + "error": err.error.to_string(), + "stage_fresh_ms": metrics.stage_fresh_ms, + "snapshot_prepare_ms": err.snapshot_prepare_ms, + "repo_sync_source": repo_outcome.repo_sync_source.as_deref(), + "repo_sync_phase": repo_outcome.repo_sync_phase.as_deref(), + "repo_sync_duration_ms": repo_outcome.repo_sync_duration_ms, + }), + ); + } + // The blocking `run_publication_point` fallback stays on the control + // thread; it is executed by the apply phase for this outcome. + return ( + StageOutcome::FreshError(Box::new(FreshErrorOutcome { + ready, + publication_point_started, + })), + metrics, + ); + } + }; + metrics.snapshot_prepare_ms = fresh_stage.snapshot_prepare_ms; + metrics.snapshot_current_index_lock_ms = + fresh_stage.snapshot_prepare_timing.current_index_lock_ms; + metrics.snapshot_manifest_load_ms = fresh_stage.snapshot_prepare_timing.manifest_load_ms; + metrics.snapshot_manifest_index_lookup_ms = + fresh_stage.snapshot_prepare_timing.manifest_index_lookup_ms; + metrics.snapshot_manifest_blob_load_ms = + fresh_stage.snapshot_prepare_timing.manifest_blob_load_ms; + metrics.snapshot_manifest_decode_ms = fresh_stage.snapshot_prepare_timing.manifest_decode_ms; + metrics.snapshot_anti_rollback_ms = fresh_stage.snapshot_prepare_timing.anti_rollback_guard_ms; + metrics.anti_rollback_meta_hit_count = + fresh_stage.snapshot_prepare_timing.anti_rollback_meta_hit as usize; + metrics.anti_rollback_meta_miss_count = + fresh_stage.snapshot_prepare_timing.anti_rollback_meta_miss as usize; + metrics.snapshot_manifest_entries_ms = fresh_stage.snapshot_prepare_timing.manifest_entries_ms; + metrics.snapshot_pack_files_ms = fresh_stage.snapshot_prepare_timing.pack_files_ms; + metrics.snapshot_pack_files_index_lookup_ms = fresh_stage + .snapshot_prepare_timing + .pack_files_index_lookup_ms; + metrics.snapshot_pack_files_blob_load_ms = + fresh_stage.snapshot_prepare_timing.pack_files_blob_load_ms; + metrics.snapshot_ee_path_validate_ms = fresh_stage.snapshot_prepare_timing.ee_path_validate_ms; + metrics.snapshot_manifest_file_count = fresh_stage.snapshot_prepare_timing.manifest_file_count; + metrics.child_discovery_ms = fresh_stage.child_discovery_ms; + if metrics.stage_fresh_ms >= crate::logging::progress::stage_fresh_slow_threshold_ms() { + crate::logging::progress::emit!( + "phase2_stage_fresh_slow", + serde_json::json!({ + "manifest_rsync_uri": ready.node.handle.manifest_rsync_uri.as_str(), + "publication_point_rsync_uri": ready.node.handle.publication_point_rsync_uri.as_str(), + "status": "ok", + "stage_fresh_ms": metrics.stage_fresh_ms, + "snapshot_prepare_ms": fresh_stage.snapshot_prepare_ms, + "snapshot_current_index_lock_ms": fresh_stage.snapshot_prepare_timing.current_index_lock_ms, + "snapshot_manifest_load_ms": fresh_stage.snapshot_prepare_timing.manifest_load_ms, + "snapshot_manifest_index_lookup_ms": fresh_stage.snapshot_prepare_timing.manifest_index_lookup_ms, + "snapshot_manifest_blob_load_ms": fresh_stage.snapshot_prepare_timing.manifest_blob_load_ms, + "snapshot_manifest_decode_ms": fresh_stage.snapshot_prepare_timing.manifest_decode_ms, + "snapshot_anti_rollback_ms": fresh_stage.snapshot_prepare_timing.anti_rollback_guard_ms, + "anti_rollback_meta_hit": fresh_stage.snapshot_prepare_timing.anti_rollback_meta_hit, + "anti_rollback_meta_miss": fresh_stage.snapshot_prepare_timing.anti_rollback_meta_miss, + "snapshot_manifest_entries_ms": fresh_stage.snapshot_prepare_timing.manifest_entries_ms, + "snapshot_pack_files_ms": fresh_stage.snapshot_prepare_timing.pack_files_ms, + "snapshot_pack_files_index_lookup_ms": fresh_stage.snapshot_prepare_timing.pack_files_index_lookup_ms, + "snapshot_pack_files_blob_load_ms": fresh_stage.snapshot_prepare_timing.pack_files_blob_load_ms, + "snapshot_ee_path_validate_ms": fresh_stage.snapshot_prepare_timing.ee_path_validate_ms, + "snapshot_manifest_file_count": fresh_stage.snapshot_prepare_timing.manifest_file_count, + "child_discovery_ms": fresh_stage.child_discovery_ms, + "child_count": fresh_stage.discovered_children.len(), + "repo_sync_source": repo_outcome.repo_sync_source.as_deref(), + "repo_sync_phase": repo_outcome.repo_sync_phase.as_deref(), + "repo_sync_duration_ms": repo_outcome.repo_sync_duration_ms, + }), + ); + } + warnings.extend(fresh_stage.warnings.clone()); + + metrics.discovered_children = fresh_stage.discovered_children.len(); + + let prepare_started = Instant::now(); + let ta_constraints = runner + .policy + .ta_constraints + .shared_for_tal(&ready.node.handle.tal_id); + if ta_constraints.is_some() + && let Some(timing) = runner.timing.as_ref() { + timing.record_count("ta_constraints_parallel_publication_points", 1); + } + match prepare_publication_point_for_parallel_roa_and_ta_constraints( + ready.node.id, + &fresh_stage.fresh_point, + runner.policy, + fresh_stage.issuer_ca_der.as_ref(), + ready.node.handle.ca_certificate_rsync_uri.as_deref(), + ready.node.handle.effective_ip_resources.as_ref(), + ready.node.handle.effective_as_resources.as_ref(), + runner.validation_time, + ta_constraints, + ) { + ParallelObjectsPrepare::Complete(mut objects) => { + metrics.prepare_ms = elapsed_ms(prepare_started); + runner.record_publication_point_step_ms( + &ready.node.handle.manifest_rsync_uri, + "fresh_objects_prepare", + metrics.prepare_ms, + ); + metrics.complete_count = 1; + metrics.roa_tasks = objects.stats.roa_total; + metrics.aspa_objects = objects.stats.aspa_total; + objects + .router_keys + .extend(fresh_stage.discovered_router_keys.clone()); + ( + StageOutcome::Complete(Box::new(CompleteOutcome { + ready, + publication_point_started, + fresh_stage, + warnings, + objects, + })), + metrics, + ) + } + ParallelObjectsPrepare::Staged(objects_stage) => { + metrics.prepare_ms = elapsed_ms(prepare_started); + runner.record_publication_point_step_ms( + &ready.node.handle.manifest_rsync_uri, + "fresh_objects_prepare", + metrics.prepare_ms, + ); + metrics.staged_count = 1; + metrics.locked_files = objects_stage.locked_file_count(); + metrics.aspa_objects = objects_stage.aspa_task_count(); + let task_count = objects_stage.roa_task_count(); + metrics.roa_tasks = task_count; + let outcome = StagedOutcome { + ready, + publication_point_started, + fresh_stage, + warnings, + objects_stage, + }; + if task_count == 0 { + metrics.zero_task_count = 1; + (StageOutcome::ZeroTask(Box::new(outcome)), metrics) + } else { + (StageOutcome::Fresh(Box::new(outcome)), metrics) + } + } + } +} + +fn apply_ready_publication_point_stage( + runner: &Rpkiv1PublicationPointRunner<'_>, + next_id: &mut u64, + ca_queue: &mut VecDeque, + pending_roa_dispatch: &mut VecDeque, + inflight_publication_points: &mut HashMap, + pending_finalization: &mut VecDeque, + finished: &mut Vec, + outcome: StageOutcome, + mut metrics: ReadyStageMetrics, + config: &TreeRunConfig, + compact_audit: bool, +) -> ReadyStageMetrics { + match outcome { + StageOutcome::FreshError(outcome) => { + let FreshErrorOutcome { + ready, + publication_point_started, + } = *outcome; + let repo_outcome = ready.repo_outcome.clone(); + metrics.fallback_count = 1; + let fallback_started = Instant::now(); + let fallback = runner.run_publication_point(&ready.node.handle); + metrics.fallback_full_run_ms = elapsed_ms(fallback_started); + if let Ok(result) = fallback.as_ref() { + metrics.discovered_children = result.discovered_children.len(); + let child_enqueue_started = Instant::now(); + enqueue_discovered_children( + next_id, + ca_queue, + &ready.node, + config, + result.discovered_children.clone(), + ); + metrics.child_enqueue_ms = elapsed_ms(child_enqueue_started); + } + finished.push(FinishedPublicationPoint { + node: FinishedPublicationPointNode::from_queued(ready.node), + result: compact_phase2_finished_result_result(fallback, compact_audit), + }); + metrics.total_ms = elapsed_ms(publication_point_started); + emit_ready_publication_point_control_slow( + metrics.manifest_rsync_uri.as_deref().unwrap_or_default(), + metrics + .publication_point_rsync_uri + .as_deref() + .unwrap_or_default(), + &repo_outcome, + &metrics, + "fallback", + true, + ); + metrics + } + StageOutcome::Complete(outcome) => { + let CompleteOutcome { + ready, + publication_point_started, + fresh_stage, + warnings, + objects, + } = *outcome; + let repo_outcome = ready.repo_outcome.clone(); + let child_enqueue_started = Instant::now(); + enqueue_discovered_children( + next_id, + ca_queue, + &ready.node, + config, + fresh_stage.discovered_children.clone(), + ); + metrics.child_enqueue_ms = elapsed_ms(child_enqueue_started); + // The finalize no longer runs synchronously here: the publication + // point is queued for the shared finalize worker through the same + // pending_finalization path (queue capacity backpressure and + // finalize_inflight accounting included) as zero-task staging. The + // per-publication-point total timing is recorded by the finalize + // worker, exactly like the zero-task and staged paths. + let direct_finalize_started = Instant::now(); + pending_finalization.push_back(FinalizeTask { + state: InflightPublicationPoint { + node: ready.node, + fresh_stage, + objects_prepare: ParallelObjectsPrepare::Complete(objects), + repo_outcome: repo_outcome.clone(), + warnings, + started_at: publication_point_started, + objects_started_at: Instant::now(), + task_count: 0, + tasks_submitted: 0, + first_task_submitted_at: None, + last_task_submitted_at: None, + first_result_at: None, + last_result_at: None, + worker_ms_total: 0, + worker_ms_max: 0, + queue_wait_ms_total: 0, + queue_wait_ms_max: 0, + finalize_enqueued_at: Some(Instant::now()), + results: Vec::new(), + }, + }); + metrics.direct_finalize_ms = elapsed_ms(direct_finalize_started); + runner.record_publication_point_step_ms( + &metrics.manifest_rsync_uri.clone().unwrap_or_default(), + "fresh_direct_finalize", + metrics.direct_finalize_ms, + ); + metrics.total_ms = elapsed_ms(publication_point_started); + emit_ready_publication_point_control_slow( + metrics.manifest_rsync_uri.as_deref().unwrap_or_default(), + metrics + .publication_point_rsync_uri + .as_deref() + .unwrap_or_default(), + &repo_outcome, + &metrics, + "complete", + false, + ); + metrics + } + StageOutcome::ZeroTask(outcome) => { + let StagedOutcome { + ready, + publication_point_started, + fresh_stage, + warnings, + objects_stage, + } = *outcome; + let repo_outcome = ready.repo_outcome.clone(); + let child_enqueue_started = Instant::now(); + enqueue_discovered_children( + next_id, + ca_queue, + &ready.node, + config, + fresh_stage.discovered_children.clone(), + ); + metrics.child_enqueue_ms = elapsed_ms(child_enqueue_started); + let build_tasks_started = Instant::now(); + objects_stage.append_roa_tasks_to(pending_roa_dispatch); + metrics.build_roa_tasks_ms = elapsed_ms(build_tasks_started); + runner.record_publication_point_step_ms( + &ready.node.handle.manifest_rsync_uri, + "fresh_build_roa_tasks", + metrics.build_roa_tasks_ms, + ); + let task_count = objects_stage.roa_task_count(); + pending_finalization.push_back(FinalizeTask { + state: InflightPublicationPoint { + node: ready.node, + fresh_stage, + objects_prepare: ParallelObjectsPrepare::Staged(objects_stage), + repo_outcome: repo_outcome.clone(), + warnings, + started_at: publication_point_started, + objects_started_at: Instant::now(), + task_count, + tasks_submitted: 0, + first_task_submitted_at: None, + last_task_submitted_at: None, + first_result_at: None, + last_result_at: None, + worker_ms_total: 0, + worker_ms_max: 0, + queue_wait_ms_total: 0, + queue_wait_ms_max: 0, + finalize_enqueued_at: Some(Instant::now()), + results: Vec::new(), + }, + }); + metrics.total_ms = elapsed_ms(publication_point_started); + emit_ready_publication_point_control_slow( + metrics.manifest_rsync_uri.as_deref().unwrap_or_default(), + metrics + .publication_point_rsync_uri + .as_deref() + .unwrap_or_default(), + &repo_outcome, + &metrics, + "zero_task", + false, + ); + metrics + } + StageOutcome::Fresh(outcome) => { + let StagedOutcome { + ready, + publication_point_started, + fresh_stage, + warnings, + objects_stage, + } = *outcome; + let repo_outcome = ready.repo_outcome.clone(); + let child_enqueue_started = Instant::now(); + enqueue_discovered_children( + next_id, + ca_queue, + &ready.node, + config, + fresh_stage.discovered_children.clone(), + ); + metrics.child_enqueue_ms = elapsed_ms(child_enqueue_started); + let build_tasks_started = Instant::now(); + objects_stage.append_roa_tasks_to(pending_roa_dispatch); + metrics.build_roa_tasks_ms = elapsed_ms(build_tasks_started); + runner.record_publication_point_step_ms( + &ready.node.handle.manifest_rsync_uri, + "fresh_build_roa_tasks", + metrics.build_roa_tasks_ms, + ); + let task_count = objects_stage.roa_task_count(); + inflight_publication_points.insert( + ready.node.id, + InflightPublicationPoint { + node: ready.node, + fresh_stage, + objects_prepare: ParallelObjectsPrepare::Staged(objects_stage), + repo_outcome: repo_outcome.clone(), + warnings, + started_at: publication_point_started, + objects_started_at: Instant::now(), + task_count, + tasks_submitted: 0, + first_task_submitted_at: None, + last_task_submitted_at: None, + first_result_at: None, + last_result_at: None, + worker_ms_total: 0, + worker_ms_max: 0, + queue_wait_ms_total: 0, + queue_wait_ms_max: 0, + finalize_enqueued_at: None, + results: Vec::with_capacity(task_count), + }, + ); + metrics.total_ms = elapsed_ms(publication_point_started); + emit_ready_publication_point_control_slow( + metrics.manifest_rsync_uri.as_deref().unwrap_or_default(), + metrics + .publication_point_rsync_uri + .as_deref() + .unwrap_or_default(), + &repo_outcome, + &metrics, + "staged", + false, + ); + metrics + } + } +} + +fn emit_ready_publication_point_control_slow( + manifest_rsync_uri: &str, + publication_point_rsync_uri: &str, + repo_outcome: &RepoSyncRuntimeOutcome, + metrics: &ReadyStageMetrics, + status: &str, + force_error_path: bool, +) { + let threshold_ms = crate::logging::progress::pp_control_slow_threshold_ms(); + if !force_error_path && metrics.total_ms < threshold_ms { + return; + } + crate::logging::progress::emit!( + "phase2_ready_publication_point_control_slow", + serde_json::json!({ + "manifest_rsync_uri": manifest_rsync_uri, + "publication_point_rsync_uri": publication_point_rsync_uri, + "status": status, + "repo_sync_source": repo_outcome.repo_sync_source.as_deref(), + "repo_sync_phase": repo_outcome.repo_sync_phase.as_deref(), + "repo_sync_duration_ms": repo_outcome.repo_sync_duration_ms, + "repo_sync_ok": repo_outcome.repo_sync_ok, + "repo_sync_err": repo_outcome.repo_sync_err.as_deref(), + "ready_queue_wait_ms": metrics.ready_queue_wait_ms, + "ready_queue_len_after_pop": metrics.ready_queue_len_after_pop, + "stage_fresh_ms": metrics.stage_fresh_ms, + "child_discovery_ms": metrics.child_discovery_ms, + "child_enqueue_ms": metrics.child_enqueue_ms, + "discovered_children": metrics.discovered_children, + "roa_presence_scan_ms": metrics.roa_presence_scan_ms, + "prepare_ms": metrics.prepare_ms, + "build_roa_tasks_ms": metrics.build_roa_tasks_ms, + "direct_finalize_ms": metrics.direct_finalize_ms, + "fallback_full_run_ms": metrics.fallback_full_run_ms, + "locked_files": metrics.locked_files, + "roa_tasks": metrics.roa_tasks, + "aspa_objects": metrics.aspa_objects, + "complete_count": metrics.complete_count, + "staged_count": metrics.staged_count, + "zero_task_count": metrics.zero_task_count, + "fallback_count": metrics.fallback_count, + "total_ms": metrics.total_ms, + "slow_threshold_ms": threshold_ms, + }), + ); + crate::logging::progress::emit!( + "phase2_ready_publication_point_control_snapshot_breakdown", + serde_json::json!({ + "manifest_rsync_uri": manifest_rsync_uri, + "publication_point_rsync_uri": publication_point_rsync_uri, + "status": status, + "snapshot_prepare_ms": metrics.snapshot_prepare_ms, + "snapshot_current_index_lock_ms": metrics.snapshot_current_index_lock_ms, + "snapshot_manifest_load_ms": metrics.snapshot_manifest_load_ms, + "snapshot_manifest_index_lookup_ms": metrics.snapshot_manifest_index_lookup_ms, + "snapshot_manifest_blob_load_ms": metrics.snapshot_manifest_blob_load_ms, + "snapshot_manifest_decode_ms": metrics.snapshot_manifest_decode_ms, + "snapshot_anti_rollback_ms": metrics.snapshot_anti_rollback_ms, + "anti_rollback_meta_hit_count": metrics.anti_rollback_meta_hit_count, + "anti_rollback_meta_miss_count": metrics.anti_rollback_meta_miss_count, + "snapshot_manifest_entries_ms": metrics.snapshot_manifest_entries_ms, + "snapshot_pack_files_ms": metrics.snapshot_pack_files_ms, + "snapshot_pack_files_index_lookup_ms": metrics.snapshot_pack_files_index_lookup_ms, + "snapshot_pack_files_blob_load_ms": metrics.snapshot_pack_files_blob_load_ms, + "snapshot_ee_path_validate_ms": metrics.snapshot_ee_path_validate_ms, + "snapshot_manifest_file_count": metrics.snapshot_manifest_file_count, + "total_ms": metrics.total_ms, + "slow_threshold_ms": threshold_ms, + }), + ); +} + +fn enqueue_discovered_children( + next_id: &mut u64, + ca_queue: &mut VecDeque, + parent: &QueuedCaInstance, + config: &TreeRunConfig, + mut children: Vec, +) { + let Some(child_depth) = next_allowed_ca_depth(config, parent.handle.depth) else { + return; + }; + + children.sort_by(|a, b| { + a.handle + .manifest_rsync_uri + .cmp(&b.handle.manifest_rsync_uri) + .then_with(|| { + a.discovered_from + .child_ca_certificate_rsync_uri + .cmp(&b.discovered_from.child_ca_certificate_rsync_uri) + }) + }); + for child in children { + let mut handle = child.handle.with_depth(child_depth); + handle.parent_manifest_rsync_uri = Some(parent.handle.manifest_rsync_uri.clone()); + ca_queue.push_back(QueuedCaInstance { + id: *next_id, + handle, + parent_id: Some(parent.id), + discovered_from: Some(child.discovered_from), + }); + *next_id += 1; + } +} + +fn finalize_metrics_from_output( + output: &FreshPublicationPointFinalizeOutput, + reduce_ms: u64, + finalize_ms: u64, + finalize_queue_wait_ms: Option, + finalize_worker_ms: u64, + locked_files: usize, +) -> FinalizePublicationPointMetrics { + FinalizePublicationPointMetrics { + reduce_ms, + finalize_ms, + finalize_queue_wait_ms, + finalize_worker_ms, + snapshot_pack_ms: output.snapshot_pack_ms, + ccr_projection_build_ms: output.ccr_projection_build_ms, + ccr_append_ms: output.ccr_append_ms, + audit_build_ms: output.audit_build_ms, + locked_files, + child_count: output.result.discovered_children.len(), + warning_count: output.result.warnings.len(), + vrp_count: output.result.objects.vrps.len(), + vap_count: output.result.objects.aspas.len(), + router_key_count: output.result.objects.router_keys.len(), + audit_object_count: output.result.audit.objects.len(), + } +} + +fn emit_finalize_breakdown( + event_name: &str, + manifest_rsync_uri: &str, + publication_point_rsync_uri: &str, + metrics: &FinalizePublicationPointMetrics, +) { + crate::logging::progress::emit!( + event_name, + serde_json::json!({ + "manifest_rsync_uri": manifest_rsync_uri, + "publication_point_rsync_uri": publication_point_rsync_uri, + "reduce_ms": metrics.reduce_ms, + "finalize_ms": metrics.finalize_ms, + "finalize_queue_wait_ms": metrics.finalize_queue_wait_ms, + "finalize_worker_ms": metrics.finalize_worker_ms, + "snapshot_pack_ms": metrics.snapshot_pack_ms, + "ccr_projection_build_ms": metrics.ccr_projection_build_ms, + "ccr_append_ms": metrics.ccr_append_ms, + "audit_build_ms": metrics.audit_build_ms, + "locked_files": metrics.locked_files, + "child_count": metrics.child_count, + "warning_count": metrics.warning_count, + "vrp_count": metrics.vrp_count, + "vap_count": metrics.vap_count, + "router_key_count": metrics.router_key_count, + "audit_object_count": metrics.audit_object_count, + }), + ); +} diff --git a/src/validation/tree_parallel/state.rs b/src/validation/tree_parallel/state.rs new file mode 100644 index 0000000..50e37e4 --- /dev/null +++ b/src/validation/tree_parallel/state.rs @@ -0,0 +1,564 @@ +#[derive(Clone, Debug)] +struct QueuedCaInstance { + id: u64, + handle: CaInstanceHandle, + parent_id: Option, + discovered_from: Option, +} + +#[derive(Clone, Debug)] +struct ReadyCaInstance { + node: QueuedCaInstance, + repo_outcome: RepoSyncRuntimeOutcome, + ready_enqueued_at: Instant, +} + +struct InflightPublicationPoint { + node: QueuedCaInstance, + fresh_stage: FreshPublicationPointStage, + objects_prepare: ParallelObjectsPrepare, + repo_outcome: RepoSyncRuntimeOutcome, + warnings: Vec, + started_at: Instant, + objects_started_at: Instant, + task_count: usize, + tasks_submitted: usize, + first_task_submitted_at: Option, + last_task_submitted_at: Option, + first_result_at: Option, + last_result_at: Option, + worker_ms_total: u64, + worker_ms_max: u64, + queue_wait_ms_total: u64, + queue_wait_ms_max: u64, + finalize_enqueued_at: Option, + results: Vec, +} + +struct FinishedPublicationPoint { + node: FinishedPublicationPointNode, + result: FinishedPublicationPointResult, +} + +#[derive(Clone, Debug)] +struct FinishedPublicationPointNode { + id: u64, + parent_id: Option, + discovered_from: Option, + manifest_rsync_uri: String, +} + +impl FinishedPublicationPointNode { + fn from_queued(node: QueuedCaInstance) -> Self { + Self { + id: node.id, + parent_id: node.parent_id, + discovered_from: node.discovered_from, + manifest_rsync_uri: node.handle.manifest_rsync_uri, + } + } +} + +#[derive(Debug)] +enum FinishedPublicationPointResult { + Ok { + warnings: Vec, + objects: ObjectsOutput, + audit: PublicationPointAudit, + }, + Err(String), +} + +struct FinalizeTask { + state: InflightPublicationPoint, +} + +/// Outcome of the pure compute phase for one ready publication point. +/// +/// `compute_ready_publication_point_stage` only performs read-only validation +/// work (repository-view lookup, fresh snapshot staging, ROA prepare) +/// and returns this enum. `apply_ready_publication_point_stage` then performs +/// every write to control-loop state (`ca_queue`/`next_id`, `finished`, +/// `pending_roa_dispatch`, `pending_finalization`, `inflight_publication_points`) +/// in the same per-publication-point order the monolithic staging function did. +/// Each variant payload is boxed so the enum itself stays small. +enum StageOutcome { + /// A repository-view request was already completed during this run and only + /// needs child enqueueing plus a `finished` entry. + /// Fresh staging failed: apply runs the existing blocking + /// `run_publication_point` fallback inline on the control thread. + FreshError(Box), + /// Fresh staging succeeded and ROA prepare returned complete objects (no + /// ROA tasks to dispatch): apply hands the publication point to the + /// finalize worker through `pending_finalization` instead of running the + /// finalize synchronously on the control thread. + Complete(Box), + /// Fresh staging succeeded with a staged objects plan that contains zero + /// ROA tasks: apply queues the finalize task directly. + ZeroTask(Box), + /// Fresh staging succeeded with ROA tasks to dispatch: apply appends the + /// tasks to `pending_roa_dispatch` and registers the inflight publication + /// point. + Fresh(Box), +} + + +struct FreshErrorOutcome { + ready: ReadyCaInstance, + publication_point_started: Instant, +} + +struct CompleteOutcome { + ready: ReadyCaInstance, + publication_point_started: Instant, + fresh_stage: FreshPublicationPointStage, + warnings: Vec, + objects: ObjectsOutput, +} + +struct StagedOutcome { + ready: ReadyCaInstance, + publication_point_started: Instant, + fresh_stage: FreshPublicationPointStage, + warnings: Vec, + objects_stage: ParallelObjectsStage, +} + +struct FinalizeWorkerResult { + finished: FinishedPublicationPoint, + metrics: FinalizePublicationPointMetrics, +} + +/// Task submitted to the experimental ready-stage worker pool: everything +/// `compute_ready_publication_point_stage` needs for one ready publication +/// point. +struct ReadyStageTask { + ready: ReadyCaInstance, + ready_queue_len_after_pop: usize, + submitted_at: Instant, +} + +/// Result drained from the ready-stage worker pool: the compute outcome and +/// its metrics plus per-task pool timing for the `phase2_stage_pool_stats` +/// observability event. +struct ReadyStageWorkerResult { + outcome: StageOutcome, + metrics: ReadyStageMetrics, + queue_wait_ms: u64, + worker_ms: u64, +} + +/// Executor borrowing the publication point runner so stage workers can run +/// the read-only compute phase off the control thread. The runner is shared +/// with the finalize worker and the ROA pool in the same way; the scoped pool +/// guarantees all borrows end before the enclosing `std::thread::scope`. +struct ReadyStageTaskExecutor<'a> { + runner: &'a Rpkiv1PublicationPointRunner<'a>, +} + +impl<'a> ObjectTaskExecutor for ReadyStageTaskExecutor<'a> { + fn execute(&self, _worker_index: usize, task: ReadyStageTask) -> ReadyStageWorkerResult { + let worker_started = Instant::now(); + let queue_wait_ms = worker_started + .saturating_duration_since(task.submitted_at) + .as_millis() as u64; + let (outcome, metrics) = compute_ready_publication_point_stage( + self.runner, + task.ready, + task.ready_queue_len_after_pop, + ); + ReadyStageWorkerResult { + outcome, + metrics, + queue_wait_ms, + worker_ms: elapsed_ms(worker_started), + } + } +} + +type ReadyStagePool<'scope, 'env> = ScopedObjectWorkerPool< + 'scope, + 'env, + ReadyStageTask, + ReadyStageWorkerResult, + ReadyStageTaskExecutor<'env>, +>; + +#[derive(Default)] +struct StageDispatchMetrics { + submitted: usize, + queue_full: bool, + duration_ms: u64, +} + +#[derive(Default)] +struct StageDrainMetrics { + results_drained: usize, + queue_wait_ms_total: u64, + queue_wait_ms_max: u64, + worker_ms_total: u64, + worker_ms_max: u64, + duration_ms: u64, +} + +#[derive(Default)] +struct ReadyStageMetrics { + manifest_rsync_uri: Option, + publication_point_rsync_uri: Option, + ready_count: usize, + fallback_count: usize, + complete_count: usize, + staged_count: usize, + zero_task_count: usize, + error_count: usize, + discovered_children: usize, + locked_files: usize, + roa_tasks: usize, + aspa_objects: usize, + stage_fresh_ms: u64, + snapshot_prepare_ms: u64, + snapshot_current_index_lock_ms: u64, + snapshot_manifest_load_ms: u64, + snapshot_manifest_index_lookup_ms: u64, + snapshot_manifest_blob_load_ms: u64, + snapshot_manifest_decode_ms: u64, + snapshot_anti_rollback_ms: u64, + anti_rollback_meta_hit_count: usize, + anti_rollback_meta_miss_count: usize, + snapshot_manifest_entries_ms: u64, + snapshot_pack_files_ms: u64, + snapshot_pack_files_index_lookup_ms: u64, + snapshot_pack_files_blob_load_ms: u64, + snapshot_ee_path_validate_ms: u64, + snapshot_manifest_file_count: usize, + child_discovery_ms: u64, + child_enqueue_ms: u64, + ready_queue_wait_ms: u64, + ready_queue_len_after_pop: usize, + roa_presence_scan_ms: u64, + direct_finalize_ms: u64, + fallback_full_run_ms: u64, + prepare_ms: u64, + build_roa_tasks_ms: u64, + total_ms: u64, +} + +#[derive(Default)] +struct ReadyStageBatchMetrics { + ready_count: usize, + fallback_count: usize, + complete_count: usize, + staged_count: usize, + zero_task_count: usize, + error_count: usize, + discovered_children: usize, + locked_files: usize, + roa_tasks: usize, + aspa_objects: usize, + stage_fresh_ms_total: u64, + stage_fresh_ms_max: u64, + stage_fresh_ms_max_manifest_rsync_uri: Option, + stage_fresh_ms_max_publication_point_rsync_uri: Option, + snapshot_prepare_ms_total: u64, + snapshot_prepare_ms_max: u64, + snapshot_current_index_lock_ms_total: u64, + snapshot_current_index_lock_ms_max: u64, + snapshot_manifest_load_ms_total: u64, + snapshot_manifest_load_ms_max: u64, + snapshot_manifest_index_lookup_ms_total: u64, + snapshot_manifest_index_lookup_ms_max: u64, + snapshot_manifest_blob_load_ms_total: u64, + snapshot_manifest_blob_load_ms_max: u64, + snapshot_manifest_decode_ms_total: u64, + snapshot_manifest_decode_ms_max: u64, + snapshot_anti_rollback_ms_total: u64, + snapshot_anti_rollback_ms_max: u64, + anti_rollback_meta_hit_count: usize, + anti_rollback_meta_miss_count: usize, + snapshot_manifest_entries_ms_total: u64, + snapshot_manifest_entries_ms_max: u64, + snapshot_pack_files_ms_total: u64, + snapshot_pack_files_ms_max: u64, + snapshot_pack_files_index_lookup_ms_total: u64, + snapshot_pack_files_index_lookup_ms_max: u64, + snapshot_pack_files_blob_load_ms_total: u64, + snapshot_pack_files_blob_load_ms_max: u64, + snapshot_ee_path_validate_ms_total: u64, + snapshot_ee_path_validate_ms_max: u64, + snapshot_manifest_file_count_total: usize, + snapshot_manifest_file_count_max: usize, + child_discovery_ms_total: u64, + child_discovery_ms_max: u64, + child_enqueue_ms_total: u64, + child_enqueue_ms_max: u64, + ready_queue_wait_ms_total: u64, + ready_queue_wait_ms_max: u64, + roa_presence_scan_ms_total: u64, + roa_presence_scan_ms_max: u64, + direct_finalize_ms_total: u64, + direct_finalize_ms_max: u64, + fallback_full_run_ms_total: u64, + fallback_full_run_ms_max: u64, + prepare_ms_total: u64, + prepare_ms_max: u64, + build_roa_tasks_ms_total: u64, + build_roa_tasks_ms_max: u64, + total_ms: u64, +} + +impl ReadyStageBatchMetrics { + fn record(&mut self, metrics: ReadyStageMetrics) { + self.ready_count += metrics.ready_count; + self.fallback_count += metrics.fallback_count; + self.complete_count += metrics.complete_count; + self.staged_count += metrics.staged_count; + self.zero_task_count += metrics.zero_task_count; + self.error_count += metrics.error_count; + self.discovered_children += metrics.discovered_children; + self.locked_files += metrics.locked_files; + self.roa_tasks += metrics.roa_tasks; + self.aspa_objects += metrics.aspa_objects; + if metrics.stage_fresh_ms >= self.stage_fresh_ms_max { + self.stage_fresh_ms_max_manifest_rsync_uri = metrics.manifest_rsync_uri.clone(); + self.stage_fresh_ms_max_publication_point_rsync_uri = + metrics.publication_point_rsync_uri.clone(); + } + self.stage_fresh_ms_total += metrics.stage_fresh_ms; + self.stage_fresh_ms_max = self.stage_fresh_ms_max.max(metrics.stage_fresh_ms); + self.snapshot_prepare_ms_total += metrics.snapshot_prepare_ms; + self.snapshot_prepare_ms_max = self + .snapshot_prepare_ms_max + .max(metrics.snapshot_prepare_ms); + self.snapshot_current_index_lock_ms_total += metrics.snapshot_current_index_lock_ms; + self.snapshot_current_index_lock_ms_max = self + .snapshot_current_index_lock_ms_max + .max(metrics.snapshot_current_index_lock_ms); + self.snapshot_manifest_load_ms_total += metrics.snapshot_manifest_load_ms; + self.snapshot_manifest_load_ms_max = self + .snapshot_manifest_load_ms_max + .max(metrics.snapshot_manifest_load_ms); + self.snapshot_manifest_index_lookup_ms_total += metrics.snapshot_manifest_index_lookup_ms; + self.snapshot_manifest_index_lookup_ms_max = self + .snapshot_manifest_index_lookup_ms_max + .max(metrics.snapshot_manifest_index_lookup_ms); + self.snapshot_manifest_blob_load_ms_total += metrics.snapshot_manifest_blob_load_ms; + self.snapshot_manifest_blob_load_ms_max = self + .snapshot_manifest_blob_load_ms_max + .max(metrics.snapshot_manifest_blob_load_ms); + self.snapshot_manifest_decode_ms_total += metrics.snapshot_manifest_decode_ms; + self.snapshot_manifest_decode_ms_max = self + .snapshot_manifest_decode_ms_max + .max(metrics.snapshot_manifest_decode_ms); + self.snapshot_anti_rollback_ms_total += metrics.snapshot_anti_rollback_ms; + self.snapshot_anti_rollback_ms_max = self + .snapshot_anti_rollback_ms_max + .max(metrics.snapshot_anti_rollback_ms); + self.anti_rollback_meta_hit_count += metrics.anti_rollback_meta_hit_count; + self.anti_rollback_meta_miss_count += metrics.anti_rollback_meta_miss_count; + self.snapshot_manifest_entries_ms_total += metrics.snapshot_manifest_entries_ms; + self.snapshot_manifest_entries_ms_max = self + .snapshot_manifest_entries_ms_max + .max(metrics.snapshot_manifest_entries_ms); + self.snapshot_pack_files_ms_total += metrics.snapshot_pack_files_ms; + self.snapshot_pack_files_ms_max = self + .snapshot_pack_files_ms_max + .max(metrics.snapshot_pack_files_ms); + self.snapshot_pack_files_index_lookup_ms_total += + metrics.snapshot_pack_files_index_lookup_ms; + self.snapshot_pack_files_index_lookup_ms_max = self + .snapshot_pack_files_index_lookup_ms_max + .max(metrics.snapshot_pack_files_index_lookup_ms); + self.snapshot_pack_files_blob_load_ms_total += metrics.snapshot_pack_files_blob_load_ms; + self.snapshot_pack_files_blob_load_ms_max = self + .snapshot_pack_files_blob_load_ms_max + .max(metrics.snapshot_pack_files_blob_load_ms); + self.snapshot_ee_path_validate_ms_total += metrics.snapshot_ee_path_validate_ms; + self.snapshot_ee_path_validate_ms_max = self + .snapshot_ee_path_validate_ms_max + .max(metrics.snapshot_ee_path_validate_ms); + self.snapshot_manifest_file_count_total += metrics.snapshot_manifest_file_count; + self.snapshot_manifest_file_count_max = self + .snapshot_manifest_file_count_max + .max(metrics.snapshot_manifest_file_count); + self.child_discovery_ms_total += metrics.child_discovery_ms; + self.child_discovery_ms_max = self.child_discovery_ms_max.max(metrics.child_discovery_ms); + self.child_enqueue_ms_total += metrics.child_enqueue_ms; + self.child_enqueue_ms_max = self.child_enqueue_ms_max.max(metrics.child_enqueue_ms); + self.ready_queue_wait_ms_total += metrics.ready_queue_wait_ms; + self.ready_queue_wait_ms_max = self + .ready_queue_wait_ms_max + .max(metrics.ready_queue_wait_ms); + self.roa_presence_scan_ms_total += metrics.roa_presence_scan_ms; + self.roa_presence_scan_ms_max = self + .roa_presence_scan_ms_max + .max(metrics.roa_presence_scan_ms); + self.direct_finalize_ms_total += metrics.direct_finalize_ms; + self.direct_finalize_ms_max = self.direct_finalize_ms_max.max(metrics.direct_finalize_ms); + self.fallback_full_run_ms_total += metrics.fallback_full_run_ms; + self.fallback_full_run_ms_max = self + .fallback_full_run_ms_max + .max(metrics.fallback_full_run_ms); + self.prepare_ms_total += metrics.prepare_ms; + self.prepare_ms_max = self.prepare_ms_max.max(metrics.prepare_ms); + self.build_roa_tasks_ms_total += metrics.build_roa_tasks_ms; + self.build_roa_tasks_ms_max = self.build_roa_tasks_ms_max.max(metrics.build_roa_tasks_ms); + self.total_ms += metrics.total_ms; + } +} + +#[derive(Default)] +struct RoaDispatchMetrics { + attempted: usize, + submitted: usize, + queue_full: bool, + pending_remaining: usize, + duration_ms: u64, +} + +#[derive(Default)] +struct ObjectDrainMetrics { + results_drained: usize, + publication_points_completed: usize, + worker_ms_total: u64, + worker_ms_max: u64, + queue_wait_ms_total: u64, + queue_wait_ms_max: u64, + result_budget_exhausted: bool, + duration_ms: u64, +} + +#[derive(Default)] +struct FinalizeSubmitMetrics { + submitted: usize, + queue_full: bool, + duration_ms: u64, +} + +#[derive(Default)] +struct FinalizePublicationPointMetrics { + reduce_ms: u64, + finalize_ms: u64, + finalize_queue_wait_ms: Option, + finalize_worker_ms: u64, + snapshot_pack_ms: u64, + ccr_projection_build_ms: u64, + ccr_append_ms: u64, + audit_build_ms: u64, + locked_files: usize, + child_count: usize, + warning_count: usize, + vrp_count: usize, + vap_count: usize, + router_key_count: usize, + audit_object_count: usize, +} + +#[derive(Default)] +struct FinalizeResultsDrainMetrics { + results_drained: usize, + reduce_ms_total: u64, + reduce_ms_max: u64, + finalize_ms_total: u64, + finalize_ms_max: u64, + finalize_queue_wait_ms_max: u64, + finalize_worker_ms_total: u64, + finalize_worker_ms_max: u64, + snapshot_pack_ms_total: u64, + snapshot_pack_ms_max: u64, + ccr_projection_build_ms_total: u64, + ccr_projection_build_ms_max: u64, + ccr_append_ms_total: u64, + ccr_append_ms_max: u64, + audit_build_ms_total: u64, + audit_build_ms_max: u64, + duration_ms: u64, +} + +#[derive(Default)] +struct RepoDrainMetrics { + event_count: usize, + completions: usize, + ready_enqueued: usize, + duration_ms: u64, +} + +const REPO_RESULT_DRAIN_MAX_EVENTS: usize = 64; + +fn elapsed_ms(started: Instant) -> u64 { + started.elapsed().as_millis() as u64 +} + +fn emit_control_loop_slow( + duration_ms: u64, + repo_poll_timeout: Duration, + repo_metrics: &RepoDrainMetrics, + ready_batch_metrics: &ReadyStageBatchMetrics, + ca_queue_len: usize, + ready_queue_len: usize, + ca_waiting_repo_identities: usize, + pending_roa_dispatch_len: usize, + inflight_publication_points_len: usize, + pending_finalization_len: usize, + finalize_inflight: usize, +) { + let threshold_ms = crate::logging::progress::control_loop_slow_threshold_ms(); + if duration_ms < threshold_ms { + return; + } + crate::logging::progress::emit!( + "phase2_control_loop_slow", + serde_json::json!({ + "duration_ms": duration_ms, + "slow_threshold_ms": threshold_ms, + "repo_poll_timeout_ms": repo_poll_timeout.as_millis() as u64, + "repo_event_count": repo_metrics.event_count, + "repo_completions": repo_metrics.completions, + "repo_ready_enqueued": repo_metrics.ready_enqueued, + "repo_drain_duration_ms": repo_metrics.duration_ms, + "ready_count": ready_batch_metrics.ready_count, + "ready_batch_duration_ms": ready_batch_metrics.total_ms, + "ready_batch_stage_fresh_ms_total": ready_batch_metrics.stage_fresh_ms_total, + "ready_batch_stage_fresh_ms_max": ready_batch_metrics.stage_fresh_ms_max, + "ready_batch_stage_fresh_ms_max_manifest_rsync_uri": ready_batch_metrics.stage_fresh_ms_max_manifest_rsync_uri, + "ready_batch_child_discovery_ms_total": ready_batch_metrics.child_discovery_ms_total, + "ready_batch_child_discovery_ms_max": ready_batch_metrics.child_discovery_ms_max, + "ready_batch_prepare_ms_total": ready_batch_metrics.prepare_ms_total, + "ready_batch_prepare_ms_max": ready_batch_metrics.prepare_ms_max, + "ready_batch_direct_finalize_ms_total": ready_batch_metrics.direct_finalize_ms_total, + "ready_batch_direct_finalize_ms_max": ready_batch_metrics.direct_finalize_ms_max, + "ca_queue_len": ca_queue_len, + "ready_queue_len": ready_queue_len, + "ca_waiting_repo_identities": ca_waiting_repo_identities, + "pending_roa_dispatch_len": pending_roa_dispatch_len, + "inflight_publication_points_len": inflight_publication_points_len, + "pending_finalization_len": pending_finalization_len, + "finalize_inflight": finalize_inflight, + }), + ); +} + +fn compact_phase2_finished_result( + mut result: PublicationPointRunResult, + compact_audit: bool, +) -> FinishedPublicationPointResult { + result.objects.audit.clear(); + if compact_audit { + result.audit.objects.clear(); + result.audit.warnings.clear(); + } + FinishedPublicationPointResult::Ok { + warnings: result.warnings, + objects: result.objects, + audit: result.audit, + } +} + +fn compact_phase2_finished_result_result( + result: Result, + compact_audit: bool, +) -> FinishedPublicationPointResult { + match result { + Ok(result) => compact_phase2_finished_result(result, compact_audit), + Err(err) => FinishedPublicationPointResult::Err(err), + } +} diff --git a/src/validation/tree_runner.rs b/src/validation/tree_runner.rs new file mode 100644 index 0000000..e69cc55 --- /dev/null +++ b/src/validation/tree_runner.rs @@ -0,0 +1,68 @@ +#![allow( + clippy::large_enum_variant, + clippy::too_many_arguments, + clippy::type_complexity +)] + +mod labels; + +use crate::ccr::CcrAccumulator; +use crate::model::rc::ResourceCertificate; +use crate::model::router_cert::{ + BgpsecRouterCertificate, BgpsecRouterCertificateDecodeError, BgpsecRouterCertificatePathError, + BgpsecRouterCertificateProfileError, +}; +use crate::output::analysis::timing::TimingHandle; +use crate::output::audit::{ + AuditObjectKind, AuditObjectResult, AuditWarning, DiscoveredFrom, ObjectAuditEntry, + PublicationPointAudit, sha256_hex, sha256_hex_from_32, +}; +use crate::output::audit_downloads::DownloadLogHandle; +use crate::output::report::{RfcRef, Warning}; +use crate::repository::current_repo_index::CurrentRepoIndexHandle; +use crate::repository::fetch::rsync::RsyncFetcher; +use crate::repository::storage::{CcrManifestProjection, PackFile, PackTime, RocksStore}; +use crate::repository::sync::repo::sync_publication_point; +use crate::repository::sync::rrdp::Fetcher; +use crate::scheduler::config::ParallelPhase2Config; +use crate::scheduler::repo_runtime::RepoSyncRuntime; +use crate::validation::ca_instance::ca_instance_uris_from_ca_certificate; +use crate::validation::ca_path::{ + CaPathError, IssuerEffectiveResourcesIndex, ValidatedSubordinateCaLite, + validate_subordinate_ca_cert_with_prevalidated_issuer_and_resources, +}; +use crate::validation::manifest::{ + FreshPublicationPointTimingBreakdown, FreshValidatedPublicationPoint, ManifestFreshError, + PublicationPointData, PublicationPointSource, + process_manifest_publication_point_fresh_after_repo_sync_with_timing, +}; +use crate::validation::objects::{ + ParallelRoaWorkerPool, RouterKeyPayload, process_publication_point_for_issuer_parallel_roa, + process_publication_point_for_issuer_parallel_roa_with_pool, +}; +use crate::validation::policy::{Policy, ResourceValidationMode}; +use crate::validation::publication_point::PublicationPointSnapshot; +use crate::validation::tree::{ + CaCertificateRef, CaInstanceHandle, DiscoveredChildCaInstance, DiscoveredChildEntryProjection, + PublicationPointRunResult, PublicationPointRunner, +}; +use labels::{ + effective_repo_sync_duration_ms, kind_from_rsync_uri, repo_sync_failure_phase_label, + repo_sync_phase_label, repo_sync_source_label, source_label, terminal_state_label, +}; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +use x509_parser::prelude::FromDer; +use x509_parser::x509::SubjectPublicKeyInfo; + +include!("tree_runner/types.rs"); + +include!("tree_runner/timing.rs"); +include!("tree_runner/fresh_pipeline.rs"); + +include!("tree_runner/publication_point_runner.rs"); +include!("tree_runner/ephemeral_state.rs"); +include!("tree_runner/discovery.rs"); +include!("tree_runner/child_validation.rs"); +include!("tree_runner/audit_projection.rs"); diff --git a/src/validation/tree_runner/audit_projection.rs b/src/validation/tree_runner/audit_projection.rs new file mode 100644 index 0000000..ac7b0fd --- /dev/null +++ b/src/validation/tree_runner/audit_projection.rs @@ -0,0 +1,164 @@ +fn build_publication_point_audit_from_snapshot( + ca: &CaInstanceHandle, + source: PublicationPointSource, + repo_sync_source: Option<&str>, + repo_sync_phase: Option<&str>, + repo_sync_duration_ms: Option, + repo_sync_error: Option<&str>, + pack: &PublicationPointSnapshot, + runner_warnings: &[Warning], + objects: &crate::validation::objects::ObjectsOutput, + child_audits: &[ObjectAuditEntry], +) -> PublicationPointAudit { + use crate::model::crl::RpkixCrl; + use std::collections::HashMap; + + let mut audit_by_uri: HashMap = pack + .files + .iter() + .map(|file| { + ( + file.rsync_uri.clone(), + ObjectAuditEntry { + rsync_uri: file.rsync_uri.clone(), + sha256_hex: sha256_hex_from_32(&file.sha256), + kind: kind_from_rsync_uri(&file.rsync_uri), + result: AuditObjectResult::Skipped, + detail: Some("skipped: not processed in this run".to_string()), + }, + ) + }) + .collect(); + + for file in &pack.files { + if !file.rsync_uri.ends_with(".crl") { + continue; + } + let decoded = file + .bytes() + .ok() + .and_then(|bytes| RpkixCrl::decode_der(bytes).ok()) + .is_some(); + audit_by_uri.insert( + file.rsync_uri.clone(), + ObjectAuditEntry { + rsync_uri: file.rsync_uri.clone(), + sha256_hex: sha256_hex_from_32(&file.sha256), + kind: AuditObjectKind::Crl, + result: if decoded { + AuditObjectResult::Ok + } else { + AuditObjectResult::Error + }, + detail: if decoded { + None + } else { + Some("CRL decode failed".to_string()) + }, + }, + ); + } + + for entry in child_audits { + audit_by_uri.insert(entry.rsync_uri.clone(), entry.clone()); + } + for entry in &objects.audit { + audit_by_uri.insert(entry.rsync_uri.clone(), entry.clone()); + } + + let mut objects_out = Vec::with_capacity(pack.files.len() + 1); + objects_out.push(ObjectAuditEntry { + rsync_uri: pack.manifest_rsync_uri.clone(), + sha256_hex: sha256_hex(&pack.manifest_bytes), + kind: AuditObjectKind::Manifest, + result: AuditObjectResult::Ok, + detail: None, + }); + for file in &pack.files { + objects_out.push(audit_by_uri.remove(&file.rsync_uri).unwrap_or_else(|| { + ObjectAuditEntry { + rsync_uri: file.rsync_uri.clone(), + sha256_hex: sha256_hex_from_32(&file.sha256), + kind: kind_from_rsync_uri(&file.rsync_uri), + result: AuditObjectResult::Skipped, + detail: Some("skipped: no audit entry".to_string()), + } + })); + } + + let mut warnings = runner_warnings.iter().map(AuditWarning::from).collect::>(); + warnings.extend(objects.warnings.iter().map(AuditWarning::from)); + PublicationPointAudit { + node_id: None, + parent_node_id: None, + discovered_from: None, + rsync_base_uri: ca.rsync_base_uri.clone(), + manifest_rsync_uri: ca.manifest_rsync_uri.clone(), + publication_point_rsync_uri: ca.publication_point_rsync_uri.clone(), + rrdp_notification_uri: ca.rrdp_notification_uri.clone(), + source: source_label(source), + repo_sync_source: repo_sync_source.map(ToString::to_string), + repo_sync_phase: repo_sync_phase.map(ToString::to_string), + repo_sync_duration_ms, + repo_sync_error: repo_sync_error.map(ToString::to_string), + repo_terminal_state: terminal_state_label(source).to_string(), + this_update_rfc3339_utc: pack.this_update.rfc3339_utc.clone(), + next_update_rfc3339_utc: pack.next_update.rfc3339_utc.clone(), + verified_at_rfc3339_utc: pack.verified_at.rfc3339_utc.clone(), + warnings, + objects: objects_out, + } +} + +fn build_publication_point_audit_from_failed_fetch( + ca: &CaInstanceHandle, + repo_sync_source: Option<&str>, + repo_sync_phase: Option<&str>, + repo_sync_duration_ms: Option, + repo_sync_error: Option<&str>, + runner_warnings: &[Warning], + fresh_error: &ManifestFreshError, +) -> PublicationPointAudit { + let mut warnings = runner_warnings.iter().map(AuditWarning::from).collect::>(); + warnings.push(AuditWarning::from( + &Warning::new(fresh_error.to_string()).with_context(&ca.manifest_rsync_uri), + )); + PublicationPointAudit { + node_id: None, + parent_node_id: None, + discovered_from: None, + rsync_base_uri: ca.rsync_base_uri.clone(), + manifest_rsync_uri: ca.manifest_rsync_uri.clone(), + publication_point_rsync_uri: ca.publication_point_rsync_uri.clone(), + rrdp_notification_uri: ca.rrdp_notification_uri.clone(), + source: source_label(PublicationPointSource::FailedFetchNoCache), + repo_sync_source: repo_sync_source.map(ToString::to_string), + repo_sync_phase: repo_sync_phase.map(ToString::to_string), + repo_sync_duration_ms, + repo_sync_error: repo_sync_error.map(ToString::to_string), + repo_terminal_state: terminal_state_label(PublicationPointSource::FailedFetchNoCache) + .to_string(), + this_update_rfc3339_utc: String::new(), + next_update_rfc3339_utc: String::new(), + verified_at_rfc3339_utc: String::new(), + warnings, + objects: vec![ObjectAuditEntry { + rsync_uri: ca.manifest_rsync_uri.clone(), + sha256_hex: String::new(), + kind: AuditObjectKind::Manifest, + result: AuditObjectResult::Error, + detail: Some(fresh_error.to_string()), + }], + } +} + +fn empty_objects_output() -> crate::validation::objects::ObjectsOutput { + crate::validation::objects::ObjectsOutput { + vrps: Vec::new(), + aspas: Vec::new(), + router_keys: Vec::new(), + warnings: Vec::new(), + stats: crate::validation::objects::ObjectsStats::default(), + audit: Vec::new(), + } +} diff --git a/src/validation/tree_runner/child_validation.rs b/src/validation/tree_runner/child_validation.rs new file mode 100644 index 0000000..a00584d --- /dev/null +++ b/src/validation/tree_runner/child_validation.rs @@ -0,0 +1,43 @@ +fn is_non_router_certificate(err: &BgpsecRouterCertificatePathError) -> bool { + matches!( + err, + BgpsecRouterCertificatePathError::Decode(BgpsecRouterCertificateDecodeError::Validate( + BgpsecRouterCertificateProfileError::NotEe + | BgpsecRouterCertificateProfileError::MissingExtendedKeyUsage + | BgpsecRouterCertificateProfileError::MissingBgpsecRouterEku + )) + ) +} + +fn validate_subordinate_ca_cert_with_issuer( + child_ca_der: &[u8], + child_ca: crate::model::rc::ResourceCertificate, + issuer_ca_der: &[u8], + issuer_ca: &crate::model::rc::ResourceCertificate, + issuer_spki: &SubjectPublicKeyInfo<'_>, + issuer_crl_uri: &str, + crl_states: &mut HashMap, + issuer_ca_rsync_uri: Option<&str>, + issuer_effective_ip: Option<&crate::model::rc::IpResourceSet>, + issuer_effective_as: Option<&crate::model::rc::AsResourceSet>, + issuer_resources_index: &IssuerEffectiveResourcesIndex, + validation_time: time::OffsetDateTime, + resource_validation_mode: ResourceValidationMode, +) -> Result { + let verified_crl = ensure_issuer_crl_verified(issuer_crl_uri, crl_states, issuer_ca_der)?; + validate_subordinate_ca_cert_with_prevalidated_issuer_and_resources( + child_ca_der, + child_ca, + issuer_ca, + issuer_spki, + &verified_crl.crl, + &verified_crl.revoked_serials, + issuer_ca_rsync_uri, + issuer_crl_uri, + issuer_effective_ip, + issuer_effective_as, + issuer_resources_index, + validation_time, + resource_validation_mode, + ) +} diff --git a/src/validation/tree_runner/discovery.rs b/src/validation/tree_runner/discovery.rs new file mode 100644 index 0000000..ca3e903 --- /dev/null +++ b/src/validation/tree_runner/discovery.rs @@ -0,0 +1,329 @@ +// Fresh child-certificate and router-certificate validation for one +// publication-point snapshot. The CRL map below is deliberately scoped to +// this call: it only avoids decoding the same CRL twice within one snapshot. + +fn discover_children_from_fresh_snapshot_with_audit_with_issuer_der( + issuer: &CaInstanceHandle, + issuer_ca_der: &[u8], + publication_point: &P, + validation_time: time::OffsetDateTime, + timing: Option<&TimingHandle>, + policy: &Policy, +) -> Result { + let locked_files = publication_point.files(); + let issuer_ca = match crate::model::rc::ResourceCertificate::decode_der(issuer_ca_der) { + Ok(ca) => match ca.validate_rfc6487_profile(crate::model::rc::ResourceCertificateRole::Ca) { + Ok(()) => Some(ca), + Err(error) => { + crate::logging::emit(crate::logging::Level::Debug, "issuer_ca_profile_error", || serde_json::json!({"error": error.to_string()})); + None + } + }, + Err(error) => { + crate::logging::emit(crate::logging::Level::Debug, "issuer_ca_decode_error", || serde_json::json!({"error": error.to_string()})); + None + } + }; + let issuer_spki = issuer_ca + .as_ref() + .and_then(|ca| SubjectPublicKeyInfo::from_der(&ca.tbs.subject_public_key_info).ok()) + .and_then(|(remaining, spki)| remaining.is_empty().then_some(spki)); + + let mut crl_states: HashMap = locked_files + .iter() + .filter(|file| file.rsync_uri.ends_with(".crl")) + .map(|file| { + let bytes = file + .bytes_cloned() + .map_err(|error| format!("snapshot CRL bytes load failed: {error}"))?; + Ok(( + file.rsync_uri.clone(), + IssuerCrlState::Pending { bytes }, + )) + }) + .collect::>()?; + + let issuer_resources_index = IssuerEffectiveResourcesIndex::from_effective_resources( + issuer.effective_ip_resources.as_ref(), + issuer.effective_as_resources.as_ref(), + ) + .map_err(|error| format!("build issuer effective resources index failed: {error}"))?; + + let mut children = Vec::new(); + let mut audits = Vec::new(); + let mut router_keys = Vec::new(); + let mut ca_ok = 0u64; + let mut ca_error = 0u64; + let mut router_ok = 0u64; + let mut router_error = 0u64; + let mut uri_errors = 0u64; + let mut decode_nanos = 0u64; + let mut validate_nanos = 0u64; + let mut uri_nanos = 0u64; + let mut child_load_nanos = 0u64; + let mut child_load_count = 0u64; + + for file in locked_files.iter().filter(|file| file.rsync_uri.ends_with(".cer")) { + let child_der = load_child_certificate_der_for_discovery( + file, + &mut child_load_nanos, + &mut child_load_count, + )?; + let decode_started = std::time::Instant::now(); + let child_cert = match crate::model::rc::ResourceCertificate::decode_der(child_der) { + Ok(value) => value, + Err(error) => { + ca_error = ca_error.saturating_add(1); + audits.push(ObjectAuditEntry { + rsync_uri: file.rsync_uri.clone(), + sha256_hex: sha256_hex_from_32(&file.sha256), + kind: AuditObjectKind::Certificate, + result: AuditObjectResult::Error, + detail: Some(format!("child certificate decode failed: {error}")), + }); + continue; + } + }; + decode_nanos = decode_nanos.saturating_add( + decode_started + .elapsed() + .as_nanos() + .min(u128::from(u64::MAX)) as u64, + ); + let issuer_crl_uri = match select_issuer_crl_uri_for_child(&child_cert, &crl_states) { + Ok(uri) => uri.to_string(), + Err(error) => { + ca_error = ca_error.saturating_add(1); + audits.push(ObjectAuditEntry { + rsync_uri: file.rsync_uri.clone(), + sha256_hex: sha256_hex_from_32(&file.sha256), + kind: AuditObjectKind::Certificate, + result: AuditObjectResult::Error, + detail: Some(format!("cannot select issuer CRL: {error}")), + }); + continue; + } + }; + let (Some(issuer_ca), Some(issuer_spki)) = (issuer_ca.as_ref(), issuer_spki.as_ref()) else { + ca_error = ca_error.saturating_add(1); + audits.push(ObjectAuditEntry { + rsync_uri: file.rsync_uri.clone(), + sha256_hex: sha256_hex_from_32(&file.sha256), + kind: AuditObjectKind::Certificate, + result: AuditObjectResult::Error, + detail: Some("issuer CA or SubjectPublicKeyInfo validation failed".to_string()), + }); + continue; + }; + + let validate_started = std::time::Instant::now(); + let validated = match validate_subordinate_ca_cert_with_issuer( + child_der, + child_cert, + issuer_ca_der, + issuer_ca, + issuer_spki, + &issuer_crl_uri, + &mut crl_states, + issuer.ca_certificate_rsync_uri.as_deref(), + issuer.effective_ip_resources.as_ref(), + issuer.effective_as_resources.as_ref(), + &issuer_resources_index, + validation_time, + policy.resource_validation_mode, + ) { + Ok(value) => value, + Err(CaPathError::ChildNotCa) => { + let router_result = match ensure_issuer_crl_verified( + &issuer_crl_uri, + &mut crl_states, + issuer_ca_der, + ) { + Ok(verified_crl) => BgpsecRouterCertificate::validate_path_with_prevalidated_issuer( + child_der, + issuer_ca, + issuer_spki, + &verified_crl.crl, + &verified_crl.revoked_serials, + issuer.ca_certificate_rsync_uri.as_deref(), + Some(issuer_crl_uri.as_str()), + validation_time, + ), + Err(error) => { + router_error = router_error.saturating_add(1); + audits.push(ObjectAuditEntry { + rsync_uri: file.rsync_uri.clone(), + sha256_hex: sha256_hex_from_32(&file.sha256), + kind: AuditObjectKind::RouterCertificate, + result: AuditObjectResult::Error, + detail: Some(format!("router certificate issuer CRL validation failed: {error}")), + }); + continue; + } + }; + match router_result { + Ok(router) => { + if let Some(constraints) = policy.ta_constraints.for_tal(&issuer.tal_id) + && let Err(error) = constraints.validate_ee_certificate(&router.resource_cert) { + router_error = router_error.saturating_add(1); + audits.push(ObjectAuditEntry { + rsync_uri: file.rsync_uri.clone(), + sha256_hex: sha256_hex_from_32(&file.sha256), + kind: AuditObjectKind::RouterCertificate, + result: AuditObjectResult::Error, + detail: Some(format!("router certificate violates TA constraints: {error}")), + }); + continue; + } + let asns = match router_asns_for_resource_mode( + &router.asns, + issuer.effective_as_resources.as_ref(), + policy.resource_validation_mode, + ) { + Ok(asns) => asns, + Err(error) => { + router_error = router_error.saturating_add(1); + audits.push(ObjectAuditEntry { + rsync_uri: file.rsync_uri.clone(), + sha256_hex: sha256_hex_from_32(&file.sha256), + kind: AuditObjectKind::RouterCertificate, + result: AuditObjectResult::Error, + detail: Some(error), + }); + continue; + } + }; + let source_hash = sha256_hex_from_32(&file.sha256); + let until = PackTime::from_utc_offset_datetime(router.resource_cert.tbs.validity_not_after); + for as_id in asns { + router_keys.push(RouterKeyPayload { + as_id, + ski: router.subject_key_identifier.clone(), + spki_der: router.spki_der.clone(), + source_object_uri: file.rsync_uri.clone(), + source_object_hash: source_hash.clone(), + source_ee_cert_hash: source_hash.clone(), + item_effective_until: until.clone(), + }); + } + router_ok = router_ok.saturating_add(1); + audits.push(ObjectAuditEntry { + rsync_uri: file.rsync_uri.clone(), + sha256_hex: source_hash, + kind: AuditObjectKind::RouterCertificate, + result: AuditObjectResult::Ok, + detail: Some("validated BGPsec router certificate (RFC 8209)".to_string()), + }); + } + Err(error) if is_non_router_certificate(&error) => { + audits.push(ObjectAuditEntry { + rsync_uri: file.rsync_uri.clone(), + sha256_hex: sha256_hex_from_32(&file.sha256), + kind: AuditObjectKind::Certificate, + result: AuditObjectResult::Skipped, + detail: Some("certificate is neither a CA nor a BGPsec router certificate".to_string()), + }); + } + Err(error) => { + router_error = router_error.saturating_add(1); + audits.push(ObjectAuditEntry { + rsync_uri: file.rsync_uri.clone(), + sha256_hex: sha256_hex_from_32(&file.sha256), + kind: AuditObjectKind::RouterCertificate, + result: AuditObjectResult::Error, + detail: Some(format!("router certificate validation failed: {error}")), + }); + } + } + continue; + } + Err(error) => { + ca_error = ca_error.saturating_add(1); + audits.push(ObjectAuditEntry { + rsync_uri: file.rsync_uri.clone(), + sha256_hex: sha256_hex_from_32(&file.sha256), + kind: AuditObjectKind::Certificate, + result: AuditObjectResult::Error, + detail: Some(format!("child CA validation failed: {error}")), + }); + continue; + } + }; + validate_nanos = validate_nanos.saturating_add( + validate_started + .elapsed() + .as_nanos() + .min(u128::from(u64::MAX)) as u64, + ); + + let uri_started = std::time::Instant::now(); + let uris = match ca_instance_uris_from_ca_certificate(&validated.child_ca) { + Ok(uris) => uris, + Err(error) => { + uri_errors = uri_errors.saturating_add(1); + audits.push(ObjectAuditEntry { + rsync_uri: file.rsync_uri.clone(), + sha256_hex: sha256_hex_from_32(&file.sha256), + kind: AuditObjectKind::Certificate, + result: AuditObjectResult::Error, + detail: Some(format!("CA instance URI discovery failed: {error}")), + }); + continue; + } + }; + uri_nanos = uri_nanos.saturating_add( + uri_started + .elapsed() + .as_nanos() + .min(u128::from(u64::MAX)) as u64, + ); + let child_ski = validated.child_ca.tbs.extensions.subject_key_identifier.clone(); + let child_hash = sha256_hex_from_32(&file.sha256); + children.push(DiscoveredChildCaInstance { + handle: CaInstanceHandle { + depth: 0, + tal_id: issuer.tal_id.clone(), + parent_manifest_rsync_uri: Some(issuer.manifest_rsync_uri.clone()), + ca_certificate: CaCertificateRef::inline_der(child_der.to_vec()), + ca_certificate_rsync_uri: Some(file.rsync_uri.clone()), + effective_ip_resources: validated.effective_ip_resources.clone(), + effective_as_resources: validated.effective_as_resources.clone(), + rsync_base_uri: uris.rsync_base_uri.clone(), + manifest_rsync_uri: uris.manifest_rsync_uri.clone(), + publication_point_rsync_uri: uris.publication_point_rsync_uri.clone(), + rrdp_notification_uri: uris.rrdp_notification_uri.clone(), + }, + discovered_from: DiscoveredFrom { + parent_manifest_rsync_uri: issuer.manifest_rsync_uri.clone(), + child_ca_certificate_rsync_uri: file.rsync_uri.clone(), + child_ca_certificate_sha256_hex: child_hash.clone(), + }, + child_entry_projection: child_ski.as_ref().map(|ski| DiscoveredChildEntryProjection { + child_ski: hex::encode(ski), + }), + }); + ca_ok = ca_ok.saturating_add(1); + audits.push(ObjectAuditEntry { + rsync_uri: file.rsync_uri.clone(), + sha256_hex: child_hash, + kind: AuditObjectKind::Certificate, + result: AuditObjectResult::Ok, + detail: Some("validated subordinate CA certificate; enqueued CA instance".to_string()), + }); + } + + if let Some(timing) = timing { + timing.record_count("child_cer_seen", locked_files.iter().filter(|file| file.rsync_uri.ends_with(".cer")).count() as u64); + timing.record_count("child_ca_ok", ca_ok); + timing.record_count("child_ca_error", ca_error); + timing.record_count("child_router_ok", router_ok); + timing.record_count("child_router_error", router_error); + timing.record_count("child_uri_discovery_error", uri_errors); + timing.record_phase_nanos("child_decode_certificate_total", decode_nanos); + timing.record_phase_nanos("child_validate_subordinate_total", validate_nanos); + timing.record_phase_nanos("child_ca_instance_uri_discovery_total", uri_nanos); + timing.record_phase_nanos("child_certificate_der_load_total", child_load_nanos); + timing.record_count("child_certificate_der_load_count", child_load_count); + } + Ok(ChildDiscoveryOutput { children, audits, router_keys }) +} diff --git a/src/validation/tree_runner/ephemeral_state.rs b/src/validation/tree_runner/ephemeral_state.rs new file mode 100644 index 0000000..17d9765 --- /dev/null +++ b/src/validation/tree_runner/ephemeral_state.rs @@ -0,0 +1,152 @@ +// Per-publication-point validation state. +// +// This module intentionally contains only ephemeral state needed while a +// snapshot is being validated. Nothing here is serialized or reused across +// runs; in particular, it is not a validation cache. + +struct ChildDiscoveryOutput { + children: Vec, + audits: Vec, + router_keys: Vec, +} + +#[derive(Clone, Debug)] +struct VerifiedIssuerCrl { + crl: crate::model::crl::RpkixCrl, + revoked_serials: std::collections::HashSet>, +} + +#[derive(Clone, Debug)] +enum IssuerCrlState { + Pending { bytes: Vec }, + Verified(VerifiedIssuerCrl), +} + + +fn load_child_certificate_der_for_discovery<'a>( + file: &'a PackFile, + elapsed_nanos: &mut u64, + count: &mut u64, +) -> Result<&'a [u8], String> { + let started = std::time::Instant::now(); + let bytes = file + .bytes() + .map_err(|e| format!("child certificate bytes load failed: {e}"))?; + *elapsed_nanos = elapsed_nanos.saturating_add( + started + .elapsed() + .as_nanos() + .min(u128::from(u64::MAX)) as u64, + ); + *count = count.saturating_add(1); + Ok(bytes) +} + +fn ca_certificate_der_for_validation<'a>( + ca: &'a CaInstanceHandle, + store: &RocksStore, + timing: Option<&TimingHandle>, +) -> Result, String> { + let started = std::time::Instant::now(); + let was_lazy = ca.ca_certificate_sha256_hex().is_some(); + let der = ca.ca_certificate_der(store)?; + if was_lazy { + let elapsed = started + .elapsed() + .as_nanos() + .min(u128::from(u64::MAX)) as u64; + if let Some(timing) = timing { + timing.record_count("ca_certificate_lazy_load_count", 1); + timing.record_count("ca_certificate_lazy_load_bytes", der.len() as u64); + timing.record_phase_nanos("ca_certificate_lazy_load_total", elapsed); + } + } + Ok(der) +} + +fn select_issuer_crl_uri_for_child<'a>( + child: &'a crate::model::rc::ResourceCertificate, + crl_states: &HashMap, +) -> Result<&'a str, String> { + if crl_states.is_empty() { + return Err( + "no CRL available in publication point snapshot (cannot validate certificates) (RFC 9286 §7; RFC 6487 §4.8.6)" + .to_string(), + ); + } + let Some(crldp_uris) = child.tbs.extensions.crl_distribution_points_uris.as_ref() else { + return Err( + "child certificate CRLDistributionPoints missing (RFC 6487 §4.8.6)".to_string(), + ); + }; + for uri in crldp_uris { + if crl_states.contains_key(uri.as_str()) { + return Ok(uri.as_str()); + } + } + Err(format!( + "CRL referenced by child certificate CRLDistributionPoints not found in publication point snapshot: {} (RFC 6487 §4.8.6; RFC 9286 §4.2.1)", + crldp_uris.iter().map(|uri| uri.as_str()).collect::>().join(", ") + )) +} + +fn ensure_issuer_crl_verified<'a>( + crl_uri: &str, + crl_states: &'a mut HashMap, + issuer_ca_der: &[u8], +) -> Result<&'a VerifiedIssuerCrl, CaPathError> { + let state = crl_states + .get_mut(crl_uri) + .expect("selected CRL must be present in snapshot"); + match state { + IssuerCrlState::Verified(verified) => Ok(verified), + IssuerCrlState::Pending { bytes } => { + let der = std::mem::take(bytes); + let crl = crate::model::crl::RpkixCrl::decode_der(&der)?; + crl.verify_signature_with_issuer_certificate_der(issuer_ca_der)?; + let mut revoked_serials = std::collections::HashSet::with_capacity(crl.revoked_certs.len()); + for revoked in &crl.revoked_certs { + revoked_serials.insert(revoked.serial_number.bytes_be.clone()); + } + *state = IssuerCrlState::Verified(VerifiedIssuerCrl { + crl, + revoked_serials, + }); + match state { + IssuerCrlState::Verified(verified) => Ok(verified), + IssuerCrlState::Pending { .. } => unreachable!(), + } + } + } +} + +fn router_asns_for_resource_mode( + router_asns: &[u32], + issuer_effective_as: Option<&crate::model::rc::AsResourceSet>, + mode: ResourceValidationMode, +) -> Result, String> { + let Some(resources) = issuer_effective_as else { + return Err("issuer has no effective AS resources".to_string()); + }; + let contains = |asn: u32| { + resources.asnum.as_ref().is_some_and(|choice| match choice { + crate::model::rc::AsIdentifierChoice::Inherit => false, + crate::model::rc::AsIdentifierChoice::AsIdsOrRanges(items) => items.iter().any(|item| match item { + crate::model::rc::AsIdOrRange::Id(id) => *id == asn, + crate::model::rc::AsIdOrRange::Range { min, max } => *min <= asn && asn <= *max, + }), + }) + }; + match mode { + ResourceValidationMode::Rfc6487 => { + let outside = router_asns.iter().copied().filter(|asn| !contains(*asn)).collect::>(); + if outside.is_empty() { Ok(router_asns.to_vec()) } else { + Err(format!("router AS resources are not a subset of issuer effective AS resources: {outside:?}")) + } + } + ResourceValidationMode::ValidationUpdate03 => { + let filtered = router_asns.iter().copied().filter(|asn| contains(*asn)).collect::>(); + if filtered.is_empty() { Err("router AS resources have empty validated resource set".to_string()) } else { Ok(filtered) } + } + } +} diff --git a/src/validation/tree_runner/fresh_pipeline.rs b/src/validation/tree_runner/fresh_pipeline.rs new file mode 100644 index 0000000..c1d1bc0 --- /dev/null +++ b/src/validation/tree_runner/fresh_pipeline.rs @@ -0,0 +1,324 @@ +impl<'a> Rpkiv1PublicationPointRunner<'a> { + pub(crate) fn ccr_accumulator_snapshot(&self) -> Option { + self.ccr_accumulator + .as_ref() + .and_then(|accumulator| accumulator.lock().ok().map(|guard| guard.clone())) + } + + pub(crate) fn append_ccr_manifest_projection( + &self, + projection: &CcrManifestProjection, + ) -> Result<(), String> { + if let Some(accumulator) = self.ccr_accumulator.as_ref() { + accumulator + .lock() + .map_err(|_| "lock CCR accumulator failed".to_string())? + .append_manifest_projection(projection)?; + } + Ok(()) + } + + pub(crate) fn stage_fresh_publication_point_after_repo_ready( + &self, + ca: &CaInstanceHandle, + repo_sync_ok: bool, + repo_sync_err: Option<&str>, + ) -> Result { + let snapshot_prepare_started = std::time::Instant::now(); + let issuer_ca_der = ca_certificate_der_for_validation(ca, self.store, self.timing.as_ref()) + .map_err(|detail| FreshPublicationPointStageError { + error: ManifestFreshError::IssuerCaLoadFailed { detail }, + snapshot_prepare_ms: snapshot_prepare_started.elapsed().as_millis() as u64, + })?; + let issuer_ca_der: Arc<[u8]> = Arc::from(issuer_ca_der.as_ref()); + let fresh_publication_point = { + let _manifest_total = self + .timing + .as_ref() + .map(|t| t.span_phase("manifest_processing_total")); + process_manifest_publication_point_fresh_after_repo_sync_with_timing( + self.store, + &ca.manifest_rsync_uri, + &ca.publication_point_rsync_uri, + self.current_repo_index.as_ref(), + issuer_ca_der.as_ref(), + ca.ca_certificate_rsync_uri.as_deref(), + self.validation_time, + repo_sync_ok, + repo_sync_err, + ) + }; + let snapshot_prepare_ms = snapshot_prepare_started.elapsed().as_millis() as u64; + let (fresh_point, snapshot_prepare_timing) = + fresh_publication_point.map_err(|error| FreshPublicationPointStageError { + error, + snapshot_prepare_ms, + })?; + if let Some(timing) = self.timing.as_ref() { + timing.record_phase_nanos( + "fresh_snapshot_prepare_total", + snapshot_prepare_ms.saturating_mul(1_000_000), + ); + timing.record_phase_nanos( + "fresh_snapshot_manifest_load_total", + snapshot_prepare_timing + .manifest_load_ms + .saturating_mul(1_000_000), + ); + timing.record_phase_nanos( + "fresh_snapshot_manifest_decode_total", + snapshot_prepare_timing + .manifest_decode_ms + .saturating_mul(1_000_000), + ); + timing.record_phase_nanos( + "fresh_snapshot_anti_rollback_total", + snapshot_prepare_timing + .anti_rollback_guard_ms + .saturating_mul(1_000_000), + ); + timing.record_phase_nanos( + "fresh_snapshot_manifest_entries_total", + snapshot_prepare_timing + .manifest_entries_ms + .saturating_mul(1_000_000), + ); + timing.record_phase_nanos( + "fresh_snapshot_pack_files_total", + snapshot_prepare_timing + .pack_files_ms + .saturating_mul(1_000_000), + ); + timing.record_phase_nanos( + "fresh_snapshot_ee_path_validate_total", + snapshot_prepare_timing + .ee_path_validate_ms + .saturating_mul(1_000_000), + ); + timing.record_count("fresh_publication_points", 1); + timing.record_count( + "fresh_manifest_files_total", + snapshot_prepare_timing.manifest_file_count as u64, + ); + } + self.record_publication_point_step_ms( + &ca.manifest_rsync_uri, + "fresh_snapshot_prepare", + snapshot_prepare_ms, + ); + self.record_publication_point_step_ms( + &ca.manifest_rsync_uri, + "fresh_snapshot_manifest_load", + snapshot_prepare_timing.manifest_load_ms, + ); + self.record_publication_point_step_ms( + &ca.manifest_rsync_uri, + "fresh_snapshot_manifest_decode", + snapshot_prepare_timing.manifest_decode_ms, + ); + self.record_publication_point_step_ms( + &ca.manifest_rsync_uri, + "fresh_snapshot_anti_rollback", + snapshot_prepare_timing.anti_rollback_guard_ms, + ); + self.record_publication_point_step_ms( + &ca.manifest_rsync_uri, + "fresh_snapshot_manifest_entries", + snapshot_prepare_timing.manifest_entries_ms, + ); + self.record_publication_point_step_ms( + &ca.manifest_rsync_uri, + "fresh_snapshot_pack_files", + snapshot_prepare_timing.pack_files_ms, + ); + self.record_publication_point_step_ms( + &ca.manifest_rsync_uri, + "fresh_snapshot_ee_path_validate", + snapshot_prepare_timing.ee_path_validate_ms, + ); + + let child_discovery_started = std::time::Instant::now(); + let out = { + let _child_disc_total = self + .timing + .as_ref() + .map(|t| t.span_phase("child_discovery_total")); + discover_children_from_fresh_snapshot_with_audit_with_issuer_der( + ca, + issuer_ca_der.as_ref(), + &fresh_point, + self.validation_time, + self.timing.as_ref(), + self.policy, + ) + }; + let (discovered_children, child_audits, discovered_router_keys, warnings) = match out { + Ok(out) => (out.children, out.audits, out.router_keys, Vec::new()), + Err(e) => ( + Vec::new(), + Vec::new(), + Vec::new(), + vec![ + Warning::new(format!("child CA discovery failed: {e}")) + .with_rfc_refs(&[RfcRef("RFC 6487 §7.2")]) + .with_context(&ca.manifest_rsync_uri), + ], + ), + }; + let child_discovery_ms = child_discovery_started.elapsed().as_millis() as u64; + if let Some(timing) = self.timing.as_ref() { + timing.record_phase_nanos( + "fresh_child_discovery_total", + child_discovery_ms.saturating_mul(1_000_000), + ); + timing.record_count( + "fresh_children_discovered", + discovered_children.len() as u64, + ); + timing.record_count("fresh_child_audits", child_audits.len() as u64); + timing.record_count( + "fresh_router_keys_discovered", + discovered_router_keys.len() as u64, + ); + } + self.record_publication_point_step_ms( + &ca.manifest_rsync_uri, + "fresh_child_discovery", + child_discovery_ms, + ); + + Ok(FreshPublicationPointStage { + fresh_point, + issuer_ca_der, + snapshot_prepare_timing, + snapshot_prepare_ms, + discovered_children, + child_audits, + discovered_router_keys, + child_discovery_ms, + warnings, + }) + } + + pub(crate) fn finalize_fresh_publication_point_from_reducer( + &self, + ca: &CaInstanceHandle, + fresh_point: &FreshValidatedPublicationPoint, + warnings: Vec, + objects: crate::validation::objects::ObjectsOutput, + child_audits: Vec, + discovered_children: Vec, + repo_sync_source: Option<&str>, + repo_sync_phase: Option<&str>, + repo_sync_duration_ms: u64, + repo_sync_err: Option<&str>, + ) -> Result { + let snapshot_pack_started = std::time::Instant::now(); + let pack = fresh_point.to_publication_point_snapshot(); + let snapshot_pack_ms = snapshot_pack_started.elapsed().as_millis() as u64; + if let Some(timing) = self.timing.as_ref() { + timing.record_phase_nanos( + "fresh_snapshot_pack_total", + snapshot_pack_ms.saturating_mul(1_000_000), + ); + } + self.record_publication_point_step_ms( + &ca.manifest_rsync_uri, + "fresh_snapshot_pack", + snapshot_pack_ms, + ); + + let mut ccr_projection_build_ms = 0; + let mut ccr_append_ms = 0; + if self.ccr_accumulator.is_some() { + let ccr_projection_build_started = std::time::Instant::now(); + let subordinate_skis = discovered_children.iter().map(|child| { + if let Some(projection) = &child.child_entry_projection { + return hex::decode(&projection.child_ski).map_err(|error| error.to_string()); + } + let der = child.handle.ca_certificate_der(self.store)?; + let certificate = ResourceCertificate::decode_der(der.as_ref()).map_err(|error| error.to_string())?; + certificate.tbs.extensions.subject_key_identifier.clone() + .ok_or_else(|| "child certificate missing SubjectKeyIdentifier".to_string()) + }).collect::, String>>()?; + let ccr_manifest_projection = crate::ccr::projection::from_snapshot(ca, &pack, subordinate_skis)?; + ccr_projection_build_ms = ccr_projection_build_started.elapsed().as_millis() as u64; + let ccr_append_started = std::time::Instant::now(); + self.append_ccr_manifest_projection(&ccr_manifest_projection)?; + ccr_append_ms = ccr_append_started.elapsed().as_millis() as u64; + } + if let Some(timing) = self.timing.as_ref() { + timing.record_phase_nanos( + "fresh_ccr_projection_build_total", + ccr_projection_build_ms.saturating_mul(1_000_000), + ); + timing.record_phase_nanos( + "fresh_ccr_append_total", + ccr_append_ms.saturating_mul(1_000_000), + ); + } + self.record_publication_point_step_ms( + &ca.manifest_rsync_uri, + "fresh_ccr_projection_build", + ccr_projection_build_ms, + ); + self.record_publication_point_step_ms( + &ca.manifest_rsync_uri, + "fresh_ccr_append", + ccr_append_ms, + ); + + let audit_build_started = std::time::Instant::now(); + let audit = build_publication_point_audit_from_snapshot( + ca, + PublicationPointSource::Fresh, + repo_sync_source, + repo_sync_phase, + Some(repo_sync_duration_ms), + repo_sync_err, + &pack, + &warnings, + &objects, + &child_audits, + ); + let audit_build_ms = audit_build_started.elapsed().as_millis() as u64; + if let Some(timing) = self.timing.as_ref() { + timing.record_phase_nanos( + "fresh_audit_build_total", + audit_build_ms.saturating_mul(1_000_000), + ); + } + self.record_publication_point_step_ms( + &ca.manifest_rsync_uri, + "fresh_audit_build", + audit_build_ms, + ); + + // Commit protocol freshness only after successful publication-point + // validation/finalization, independently of optional CCR output. + self.store.put_manifest_anti_rollback_meta( + &crate::repository::storage::ManifestAntiRollbackMeta { + manifest_rsync_uri: pack.manifest_rsync_uri.clone(), + manifest_number_be: pack.manifest_number_be.clone(), + manifest_this_update: pack.this_update.clone(), + manifest_sha256: ::digest(&pack.manifest_bytes).to_vec(), + updated_at_validation_time: pack.verified_at.clone(), + }, + ).map_err(|error| format!("persist manifest freshness failed: {error}"))?; + + Ok(FreshPublicationPointFinalizeOutput { + result: PublicationPointRunResult { + source: PublicationPointSource::Fresh, + snapshot: Some(pack), + warnings, + objects, + audit, + discovered_children, + }, + snapshot_pack_ms, + ccr_projection_build_ms, + ccr_append_ms, + audit_build_ms, + }) + } +} diff --git a/src/validation/tree_runner/labels.rs b/src/validation/tree_runner/labels.rs new file mode 100644 index 0000000..98f3449 --- /dev/null +++ b/src/validation/tree_runner/labels.rs @@ -0,0 +1,86 @@ +//! Stable labels used by validation audit output. +//! +//! These helpers are pure mappings. Keeping them separate from publication +//! point traversal makes the output vocabulary easy to review without +//! changing traversal or validation control flow. + +use crate::output::audit::AuditObjectKind; +use crate::validation::manifest::PublicationPointSource; + +pub(super) fn kind_from_rsync_uri(uri: &str) -> AuditObjectKind { + if uri.ends_with(".crl") { + AuditObjectKind::Crl + } else if uri.ends_with(".cer") { + AuditObjectKind::Certificate + } else if uri.ends_with(".roa") { + AuditObjectKind::Roa + } else if uri.ends_with(".asa") { + AuditObjectKind::Aspa + } else { + AuditObjectKind::Other + } +} + +pub(super) fn source_label(source: PublicationPointSource) -> String { + match source { + PublicationPointSource::Fresh => "fresh".to_string(), + PublicationPointSource::FailedFetchNoCache => "failed_fetch_no_cache".to_string(), + } +} + +pub(super) fn repo_sync_phase_label( + phase: crate::repository::sync::repo::RepoSyncPhase, +) -> &'static str { + match phase { + crate::repository::sync::repo::RepoSyncPhase::RrdpOk => "rrdp_ok", + crate::repository::sync::repo::RepoSyncPhase::RrdpFailedRsyncOk => "rrdp_failed_rsync_ok", + crate::repository::sync::repo::RepoSyncPhase::RsyncOnlyOk => "rsync_only_ok", + } +} + +pub(super) fn repo_sync_failure_phase_label( + attempted_rrdp: bool, + original_notification_uri: Option<&str>, + effective_notification_uri: Option<&str>, +) -> &'static str { + if attempted_rrdp && original_notification_uri.is_some() && effective_notification_uri.is_some() + { + "rrdp_failed_rsync_failed" + } else if attempted_rrdp + && original_notification_uri.is_some() + && effective_notification_uri.is_none() + { + "rsync_only_failed_after_rrdp_dedup" + } else { + "rsync_only_failed" + } +} + +pub(super) fn terminal_state_label(source: PublicationPointSource) -> &'static str { + match source { + PublicationPointSource::Fresh => "fresh", + PublicationPointSource::FailedFetchNoCache => "failed_no_cache", + } +} + +pub(super) fn repo_sync_source_label( + source: crate::repository::sync::repo::RepoSyncSource, +) -> &'static str { + match source { + crate::repository::sync::repo::RepoSyncSource::Rrdp => "rrdp", + crate::repository::sync::repo::RepoSyncSource::Rsync => "rsync", + } +} + +pub(super) fn effective_repo_sync_duration_ms( + elapsed_ms: u64, + runtime_reported_duration_ms: Option, + repo_sync_ok: bool, +) -> u64 { + if repo_sync_ok { + return elapsed_ms; + } + runtime_reported_duration_ms + .map(|runtime_ms| elapsed_ms.max(runtime_ms)) + .unwrap_or(elapsed_ms) +} diff --git a/src/validation/tree_runner/publication_point_runner.rs b/src/validation/tree_runner/publication_point_runner.rs new file mode 100644 index 0000000..017a150 --- /dev/null +++ b/src/validation/tree_runner/publication_point_runner.rs @@ -0,0 +1,223 @@ +impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> { + fn run_publication_point( + &self, + ca: &CaInstanceHandle, + ) -> Result { + let started = std::time::Instant::now(); + let _span = self + .timing + .as_ref() + .map(|timing| timing.span_publication_point(&ca.manifest_rsync_uri)); + if let Some(timing) = self.timing.as_ref() { + timing.record_count("publication_points_seen", 1); + } + crate::logging::progress::emit!( + "publication_point_start", + serde_json::json!({ + "manifest_rsync_uri": ca.manifest_rsync_uri, + "publication_point_rsync_uri": ca.publication_point_rsync_uri, + "rsync_base_uri": ca.rsync_base_uri, + "rrdp_notification_uri": ca.rrdp_notification_uri, + }), + ); + + let attempted_rrdp = + self.policy.sync_preference == crate::validation::policy::SyncPreference::RrdpThenRsync; + let repo_started = std::time::Instant::now(); + let (repo_sync_ok, repo_sync_error, repo_sync_source, repo_sync_phase, runtime_duration) = + if let Some(runtime) = self.repo_sync_runtime.as_ref() { + let outcome = runtime.sync_publication_point_repo(ca)?; + ( + outcome.repo_sync_ok, + outcome.repo_sync_err, + outcome.repo_sync_source, + outcome.repo_sync_phase, + Some(outcome.repo_sync_duration_ms), + ) + } else { + match sync_publication_point( + self.store, + self.policy, + if attempted_rrdp { + ca.rrdp_notification_uri.as_deref() + } else { + None + }, + &ca.rsync_base_uri, + self.http_fetcher, + self.rsync_fetcher, + self.timing.as_ref(), + self.download_log.as_ref(), + ) { + Ok(result) => ( + true, + None, + Some(repo_sync_source_label(result.source).to_string()), + Some(repo_sync_phase_label(result.phase).to_string()), + None, + ), + Err(error) => ( + false, + Some(error.to_string()), + None, + Some( + repo_sync_failure_phase_label( + attempted_rrdp, + ca.rrdp_notification_uri.as_deref(), + ca.rrdp_notification_uri.as_deref(), + ) + .to_string(), + ), + None, + ), + } + }; + let repo_sync_duration_ms = effective_repo_sync_duration_ms( + repo_started.elapsed().as_millis() as u64, + runtime_duration, + repo_sync_ok, + ); + crate::logging::progress::emit!( + "publication_point_repo_sync_done", + serde_json::json!({ + "manifest_rsync_uri": ca.manifest_rsync_uri, + "repo_sync_ok": repo_sync_ok, + "repo_sync_source": repo_sync_source, + "repo_sync_phase": repo_sync_phase, + "repo_sync_error": repo_sync_error, + "repo_sync_duration_ms": repo_sync_duration_ms, + }), + ); + + let stage = self.stage_fresh_publication_point_after_repo_ready( + ca, + repo_sync_ok, + repo_sync_error.as_deref(), + ); + let stage = match stage { + Ok(stage) => stage, + Err(stage_error) => { + let mut warnings = Vec::new(); + warnings.push( + Warning::new(format!("publication point processing failed: {}", stage_error.error)) + .with_rfc_refs(&[RfcRef("RFC 9286 §6.6")]) + .with_context(&ca.manifest_rsync_uri), + ); + let audit = build_publication_point_audit_from_failed_fetch( + ca, + repo_sync_source.as_deref(), + repo_sync_phase.as_deref(), + Some(repo_sync_duration_ms), + repo_sync_error.as_deref(), + &warnings, + &stage_error.error, + ); + crate::logging::progress::emit!( + "repo_terminal_failure", + serde_json::json!({ + "manifest_rsync_uri": ca.manifest_rsync_uri, + "repo_sync_source": repo_sync_source, + "repo_sync_phase": repo_sync_phase, + "repo_sync_error": repo_sync_error, + "repo_sync_duration_ms": repo_sync_duration_ms, + "terminal_state": "failed_no_cache", + "error": stage_error.error.to_string(), + }), + ); + return Ok(PublicationPointRunResult { + source: PublicationPointSource::FailedFetchNoCache, + snapshot: None, + warnings, + objects: empty_objects_output(), + audit, + discovered_children: Vec::new(), + }); + } + }; + + let ta_constraints = self.policy.ta_constraints.for_tal(&ca.tal_id); + let object_started = std::time::Instant::now(); + let mut objects = if let Some(constraints) = ta_constraints { + crate::validation::objects::process_publication_point_for_issuer_with_ta_constraints( + &stage.fresh_point, + self.policy, + stage.issuer_ca_der.as_ref(), + ca.ca_certificate_rsync_uri.as_deref(), + ca.effective_ip_resources.as_ref(), + ca.effective_as_resources.as_ref(), + self.validation_time, + self.timing.as_ref(), + Some(constraints), + ) + } else if let Some(pool) = self.parallel_roa_worker_pool.as_ref() { + process_publication_point_for_issuer_parallel_roa_with_pool( + &stage.fresh_point, + self.policy, + stage.issuer_ca_der.as_ref(), + ca.ca_certificate_rsync_uri.as_deref(), + ca.effective_ip_resources.as_ref(), + ca.effective_as_resources.as_ref(), + self.validation_time, + self.timing.as_ref(), + pool, + ) + } else if let Some(config) = self.parallel_phase2_config.as_ref() { + process_publication_point_for_issuer_parallel_roa( + &stage.fresh_point, + self.policy, + stage.issuer_ca_der.as_ref(), + ca.ca_certificate_rsync_uri.as_deref(), + ca.effective_ip_resources.as_ref(), + ca.effective_as_resources.as_ref(), + self.validation_time, + self.timing.as_ref(), + config, + ) + } else { + crate::validation::objects::process_publication_point_for_issuer( + &stage.fresh_point, + self.policy, + stage.issuer_ca_der.as_ref(), + ca.ca_certificate_rsync_uri.as_deref(), + ca.effective_ip_resources.as_ref(), + ca.effective_as_resources.as_ref(), + self.validation_time, + self.timing.as_ref(), + ) + }; + let object_ms = object_started.elapsed().as_millis() as u64; + self.record_publication_point_step_ms(&ca.manifest_rsync_uri, "objects_processing", object_ms); + objects.router_keys.extend(stage.discovered_router_keys.clone()); + + let finalized = self.finalize_fresh_publication_point_from_reducer( + ca, + &stage.fresh_point, + stage.warnings, + objects, + stage.child_audits, + stage.discovered_children, + repo_sync_source.as_deref(), + repo_sync_phase.as_deref(), + repo_sync_duration_ms, + repo_sync_error.as_deref(), + )?; + let result = finalized.result; + let total_ms = started.elapsed().as_millis() as u64; + crate::logging::progress::emit!( + "publication_point_finish", + serde_json::json!({ + "manifest_rsync_uri": ca.manifest_rsync_uri, + "repo_sync_source": repo_sync_source, + "repo_sync_phase": repo_sync_phase, + "repo_sync_duration_ms": repo_sync_duration_ms, + "total_duration_ms": total_ms, + "objects_processing_ms": object_ms, + "warning_count": result.warnings.len(), + "vrp_count": result.objects.vrps.len(), + "aspa_count": result.objects.aspas.len(), + "child_count": result.discovered_children.len(), + }), + ); + Ok(result) + } +} diff --git a/src/validation/tree_runner/timing.rs b/src/validation/tree_runner/timing.rs new file mode 100644 index 0000000..5869710 --- /dev/null +++ b/src/validation/tree_runner/timing.rs @@ -0,0 +1,23 @@ +impl<'a> Rpkiv1PublicationPointRunner<'a> { + pub(crate) fn record_publication_point_total_ms(&self, manifest_rsync_uri: &str, ms: u64) { + if let Some(timing) = self.timing.as_ref() { + timing.record_publication_point_nanos(manifest_rsync_uri, ms.saturating_mul(1_000_000)); + } + } + + pub(crate) fn record_publication_point_step_ms( + &self, + manifest_rsync_uri: &str, + step: &'static str, + ms: u64, + ) { + if let Some(timing) = self.timing.as_ref() { + timing.record_publication_point_step_nanos( + manifest_rsync_uri, + step, + ms.saturating_mul(1_000_000), + ); + } + } + +} diff --git a/src/validation/tree_runner/types.rs b/src/validation/tree_runner/types.rs new file mode 100644 index 0000000..acc038f --- /dev/null +++ b/src/validation/tree_runner/types.rs @@ -0,0 +1,42 @@ +#[derive(Clone, Debug)] +pub(crate) struct FreshPublicationPointStage { + pub(crate) fresh_point: FreshValidatedPublicationPoint, + pub(crate) issuer_ca_der: Arc<[u8]>, + pub(crate) snapshot_prepare_timing: FreshPublicationPointTimingBreakdown, + pub(crate) snapshot_prepare_ms: u64, + pub(crate) discovered_children: Vec, + pub(crate) child_audits: Vec, + pub(crate) discovered_router_keys: Vec, + pub(crate) child_discovery_ms: u64, + pub(crate) warnings: Vec, +} + +#[derive(Debug)] +pub(crate) struct FreshPublicationPointStageError { + pub(crate) error: ManifestFreshError, + pub(crate) snapshot_prepare_ms: u64, +} + +#[derive(Clone, Debug)] +pub(crate) struct FreshPublicationPointFinalizeOutput { + pub(crate) result: PublicationPointRunResult, + pub(crate) snapshot_pack_ms: u64, + pub(crate) ccr_projection_build_ms: u64, + pub(crate) ccr_append_ms: u64, + pub(crate) audit_build_ms: u64, +} + +pub struct Rpkiv1PublicationPointRunner<'a> { + pub store: &'a RocksStore, + pub policy: &'a Policy, + pub http_fetcher: &'a dyn Fetcher, + pub rsync_fetcher: &'a dyn RsyncFetcher, + pub validation_time: time::OffsetDateTime, + pub timing: Option, + pub download_log: Option, + pub current_repo_index: Option, + pub repo_sync_runtime: Option>, + pub parallel_phase2_config: Option, + pub parallel_roa_worker_pool: Option, + pub ccr_accumulator: Option>, +} diff --git a/src/validation/x509_name.rs b/src/validation/x509_name.rs new file mode 100644 index 0000000..c943442 --- /dev/null +++ b/src/validation/x509_name.rs @@ -0,0 +1,85 @@ +use crate::model::common::X509NameDer; +use x509_parser::prelude::FromDer; + +fn canonicalize(name: &X509NameDer) -> Option { + let (rem, parsed) = x509_parser::x509::X509Name::from_der(name.as_raw()).ok()?; + if !rem.is_empty() { + return None; + } + Some(parsed.to_string()) +} + +/// Compare two X.509 distinguished names using a tolerant semantic comparison. +/// +/// RPKI repositories in the wild sometimes encode the same name using different +/// ASN.1 string types (e.g., PrintableString vs UTF8String) while remaining +/// semantically equivalent. RFC 5280 path validation requires name matching, but +/// DER byte equality is too strict for interoperability. +pub fn x509_names_equivalent(a: &X509NameDer, b: &X509NameDer) -> bool { + let Some(ca) = canonicalize(a) else { + return a == b; + }; + let Some(cb) = canonicalize(b) else { + return a == b; + }; + ca == cb +} + +#[cfg(test)] +mod tests { + use super::*; + + fn synthetic_certificate_der() -> Vec { + std::fs::read( + crate::test_support::synthetic_repository() + .case_repository("baseline-v1") + .join("root/child.cer"), + ) + .expect("read synthetic child certificate") + } + + #[test] + fn x509_names_equivalent_falls_back_to_der_equality_when_parse_fails() { + // Invalid tag (not a SEQUENCE) makes x509-parser fail and forces DER equality fallback. + let a = X509NameDer(vec![0x01, 0x00]); + let b = X509NameDer(vec![0x01, 0x00]); + assert!(x509_names_equivalent(&a, &b)); + } + + #[test] + fn x509_names_equivalent_compares_semantic_names_when_parse_succeeds() { + let cert_der = synthetic_certificate_der(); + + let (_rem, cert) = + x509_parser::parse_x509_certificate(&cert_der).expect("parse certificate fixture"); + + let subject = X509NameDer(cert.tbs_certificate.subject.as_raw().to_vec()); + let issuer = X509NameDer(cert.tbs_certificate.issuer.as_raw().to_vec()); + + assert!(x509_names_equivalent(&subject, &subject)); + assert!(!x509_names_equivalent(&subject, &issuer)); + } + + #[test] + fn x509_names_equivalent_falls_back_when_name_has_trailing_bytes() { + // Use a real name DER and append a trailing byte so parsing yields leftover `rem`. + let cert_der = synthetic_certificate_der(); + let (_rem, cert) = + x509_parser::parse_x509_certificate(&cert_der).expect("parse certificate fixture"); + let mut name = cert.tbs_certificate.subject.as_raw().to_vec(); + name.push(0x00); + let a = X509NameDer(name.clone()); + let b = X509NameDer(name); + assert!(x509_names_equivalent(&a, &b)); + } + + #[test] + fn x509_names_equivalent_falls_back_when_one_side_fails_to_parse() { + let cert_der = synthetic_certificate_der(); + let (_rem, cert) = + x509_parser::parse_x509_certificate(&cert_der).expect("parse certificate fixture"); + let good = X509NameDer(cert.tbs_certificate.subject.as_raw().to_vec()); + let bad = X509NameDer(vec![0x01, 0x00]); + assert!(!x509_names_equivalent(&good, &bad)); + } +} diff --git a/tests/daemon_e2e.rs b/tests/daemon_e2e.rs new file mode 100644 index 0000000..b4d9965 --- /dev/null +++ b/tests/daemon_e2e.rs @@ -0,0 +1,9 @@ +#[test] +fn real_daemon_lifecycle() { + let status = std::process::Command::new("python3") + .arg("tests/support/test_daemon.py") + .arg(env!("CARGO_BIN_EXE_panda-rpki")) + .status() + .expect("start daemon integration driver"); + assert!(status.success(), "real daemon integration failed: {status}"); +} diff --git a/tests/support/README.md b/tests/support/README.md new file mode 100644 index 0000000..2f60319 --- /dev/null +++ b/tests/support/README.md @@ -0,0 +1,13 @@ +# Synthetic RPKI test data + +`generate_repository.py` creates a fresh RFC-shaped RPKI hierarchy with new +keys for each test process. It provides TAL/TA bootstrap files, CA/CRL, +manifest, ROA, ASPA, RRDP snapshot and delta documents, and a locally issued +HTTPS certificate. `serve_repository.py` is the deliberately minimal HTTPS +server used by the deterministic runtime and Docker tests. + +No RIR production TAL, certificate, publication-point object, cache, or live +network request is included in this directory. The generator requires Python +`cryptography`, OpenSSL's `cms` command, and Python 3; CI installs those test +tools explicitly. Test output is created under a temporary directory and is +not part of a release artifact. diff --git a/tests/support/generate_repository.py b/tests/support/generate_repository.py new file mode 100644 index 0000000..3d2849f --- /dev/null +++ b/tests/support/generate_repository.py @@ -0,0 +1,717 @@ +#!/usr/bin/env python3 +"""Generate synthetic RFC RPKI fixtures using cryptography and OpenSSL. + +Outputs use new test keys and documentation resources; no downloaded RIR objects. +""" +from __future__ import annotations + +import argparse +import base64 +import hashlib +import ipaddress +import shutil +import subprocess +import tempfile +from datetime import datetime, timedelta, timezone +from pathlib import Path + +from cryptography import x509 +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.serialization import Encoding, NoEncryption, PrivateFormat, PublicFormat +from cryptography.x509 import AccessDescription, AuthorityInformationAccess, DistributionPoint, UniformResourceIdentifier +from cryptography.x509.oid import AuthorityInformationAccessOID, NameOID, ObjectIdentifier + + +RRDP_HOST = "rpki-local-rrdp" +FOREIGN_RRDP_HOST = "rpki-local-foreign" +RRDP_PORT = 8443 +RSYNC_HOST = "rpki-local-rsync" +RSYNC_PORT = 873 +TAL_URI = f"rsync://{RSYNC_HOST}:{RSYNC_PORT}/custom/ta.cer" +TAL_HTTPS_URI = f"https://{RRDP_HOST}:{RRDP_PORT}/ta/custom-ta.cer" +RRDP_URI = f"https://{RRDP_HOST}:{RRDP_PORT}/rrdp/notification.xml" +ROOT_REPO_URI = f"rsync://{RSYNC_HOST}:{RSYNC_PORT}/custom/root/" +ROOT_MFT_URI = f"{ROOT_REPO_URI}root.mft" +CHILD_REPO_URI = f"rsync://{RSYNC_HOST}:{RSYNC_PORT}/custom/child/" +CHILD_MFT_URI = f"{CHILD_REPO_URI}child.mft" +OID_SIA = ObjectIdentifier("1.3.6.1.5.5.7.1.11") +OID_RESOURCES_IP = ObjectIdentifier("1.3.6.1.5.5.7.1.7") +OID_RESOURCES_AS = ObjectIdentifier("1.3.6.1.5.5.7.1.8") +OID_POLICY_IP_AS = ObjectIdentifier("1.3.6.1.5.5.7.14.2") +OID_ROA_ECONTENT = "1.2.840.113549.1.9.16.1.24" +OID_MANIFEST_ECONTENT = "1.2.840.113549.1.9.16.1.26" +OID_ASPA_ECONTENT = "1.2.840.113549.1.9.16.1.49" +OID_SHA256 = "2.16.840.1.101.3.4.2.1" +SIA_CA_REPOSITORY = "1.3.6.1.5.5.7.48.5" +SIA_RPKI_MANIFEST = "1.3.6.1.5.5.7.48.10" +SIA_SIGNED_OBJECT = "1.3.6.1.5.5.7.48.11" +SIA_RPKI_NOTIFY = "1.3.6.1.5.5.7.48.13" +UTC = timezone.utc +VALID_FROM = datetime(2026, 1, 1, tzinfo=UTC) +VALID_TO = datetime(2027, 12, 31, 23, 59, 59, tzinfo=UTC) +CHILD_PREFIX = ipaddress.ip_network("203.0.113.0/24") +SECOND_PREFIX = ipaddress.ip_network("203.0.113.128/25") +OUTSIDE_PREFIX = ipaddress.ip_network("203.0.114.0/24") +TEST_ASN = 64496 +SESSION_ID = "11111111-2222-4333-8444-555555555555" + + +def tlv(tag: int, value: bytes) -> bytes: + length = len(value) + if length < 128: + encoded_length = bytes([length]) + else: + raw = length.to_bytes((length.bit_length() + 7) // 8, "big") + encoded_length = bytes([0x80 | len(raw)]) + raw + return bytes([tag]) + encoded_length + value + + +def seq(*values: bytes) -> bytes: + return tlv(0x30, b"".join(values)) + + +def integer(value: int) -> bytes: + raw = value.to_bytes(max(1, (value.bit_length() + 7) // 8), "big") + if raw[0] & 0x80: + raw = b"\x00" + raw + return tlv(0x02, raw) + + +def oid(oid_value: str) -> bytes: + parts = [int(part) for part in oid_value.split(".")] + encoded = bytearray([40 * parts[0] + parts[1]]) + for part in parts[2:]: + chunks = [part & 0x7F] + part >>= 7 + while part: + chunks.append(0x80 | (part & 0x7F)) + part >>= 7 + encoded.extend(reversed(chunks)) + return tlv(0x06, bytes(encoded)) + + +def octet(value: bytes) -> bytes: + return tlv(0x04, value) + + +def uri(value: str) -> bytes: + return tlv(0x86, value.encode("ascii")) + + +def bit_string(value: bytes, unused: int = 0) -> bytes: + return tlv(0x03, bytes([unused]) + value) + + +def generalized_time(value: datetime) -> bytes: + return tlv(0x18, value.astimezone(UTC).strftime("%Y%m%d%H%M%SZ").encode("ascii")) + + +def context_zero(value: bytes) -> bytes: + return tlv(0xA0, value) + + +def ip_choice(networks: list[ipaddress._BaseNetwork]) -> bytes: + entries = [] + for network in networks: + length = (network.prefixlen + 7) // 8 + raw = network.network_address.packed[:length] + entries.append(bit_string(raw, length * 8 - network.prefixlen)) + return seq(*entries) + + +def ip_resources(networks: list[ipaddress._BaseNetwork]) -> bytes: + families: dict[int, list[ipaddress._BaseNetwork]] = {1: [], 2: []} + for network in networks: + families[1 if network.version == 4 else 2].append(network) + return seq( + *( + seq(octet(afi.to_bytes(2, "big")), ip_choice(items)) + for afi, items in families.items() + if items + ) + ) + + +def ip_inherit() -> bytes: + return seq( + seq(octet(b"\x00\x01"), tlv(0x05, b"")), + seq(octet(b"\x00\x02"), tlv(0x05, b"")), + ) + + +def as_resources(asns: list[int] | None) -> bytes: + if asns is None: + return seq(context_zero(tlv(0x05, b""))) + return seq(context_zero(seq(*(integer(asn) for asn in asns)))) + + +def sia(entries: list[tuple[str, str]]) -> bytes: + return seq(*(seq(oid(method), uri(location)) for method, location in entries)) + + +def write(path: Path, data: bytes, mode: int | None = None) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(data) + if mode is not None: + path.chmod(mode) + + +def pem_cert(cert: x509.Certificate) -> bytes: + return cert.public_bytes(Encoding.PEM) + + +def pem_key(key: rsa.RSAPrivateKey) -> bytes: + return key.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption()) + + +def write_key(path: Path, key: rsa.RSAPrivateKey) -> None: + write(path, pem_key(key), 0o600) + + +def sign_cert( + subject: x509.Name, + issuer: x509.Name, + public_key, + signing_key: rsa.RSAPrivateKey, + serial: int, + is_ca: bool, + ski_key: rsa.RSAPrivateKey, + aki_key: rsa.RSAPrivateKey | None = None, + ip_ext: bytes | None = None, + as_ext: bytes | None = None, + ca_repository: str | None = None, + manifest_uri: str | None = None, + notify_uri: str | None = None, + signed_object_uri: str | None = None, + crl_uri: str | None = None, + ca_issuer_uri: str | None = None, + not_before: datetime = VALID_FROM, + not_after: datetime = VALID_TO, +) -> x509.Certificate: + builder = ( + x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(issuer) + .public_key(public_key) + .serial_number(serial) + .not_valid_before(not_before) + .not_valid_after(not_after) + ) + if is_ca: + builder = builder.add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True) + if is_ca: + usage = x509.KeyUsage(False, False, False, False, False, True, True, False, False) + else: + usage = x509.KeyUsage(True, False, False, False, False, False, False, False, False) + builder = builder.add_extension(usage, critical=True) + builder = builder.add_extension(x509.SubjectKeyIdentifier.from_public_key(ski_key.public_key()), critical=False) + if aki_key is not None: + builder = builder.add_extension(x509.AuthorityKeyIdentifier.from_issuer_public_key(aki_key.public_key()), critical=False) + builder = builder.add_extension( + x509.CertificatePolicies([x509.PolicyInformation(OID_POLICY_IP_AS, None)]), + critical=True, + ) + if crl_uri is not None: + builder = builder.add_extension( + x509.CRLDistributionPoints( + [DistributionPoint([UniformResourceIdentifier(crl_uri)], None, None, None)] + ), + critical=False, + ) + if ca_issuer_uri is not None: + builder = builder.add_extension( + AuthorityInformationAccess( + [AccessDescription(AuthorityInformationAccessOID.CA_ISSUERS, UniformResourceIdentifier(ca_issuer_uri))] + ), + critical=False, + ) + sia_entries: list[tuple[str, str]] = [] + if ca_repository is not None: + sia_entries.append((SIA_CA_REPOSITORY, ca_repository)) + if manifest_uri is not None: + sia_entries.append((SIA_RPKI_MANIFEST, manifest_uri)) + if notify_uri is not None: + sia_entries.append((SIA_RPKI_NOTIFY, notify_uri)) + if signed_object_uri is not None: + sia_entries.append((SIA_SIGNED_OBJECT, signed_object_uri)) + if sia_entries: + builder = builder.add_extension(x509.UnrecognizedExtension(OID_SIA, sia(sia_entries)), critical=False) + if ip_ext is not None: + builder = builder.add_extension(x509.UnrecognizedExtension(OID_RESOURCES_IP, ip_ext), critical=True) + if as_ext is not None: + builder = builder.add_extension(x509.UnrecognizedExtension(OID_RESOURCES_AS, as_ext), critical=True) + return builder.sign(signing_key, hashes.SHA256()) + + +def make_crl(subject: x509.Name, key: rsa.RSAPrivateKey, number: int) -> x509.CertificateRevocationList: + return ( + x509.CertificateRevocationListBuilder() + .issuer_name(subject) + .last_update(VALID_FROM + timedelta(days=30)) + .next_update(VALID_TO - timedelta(days=30)) + .add_extension(x509.AuthorityKeyIdentifier.from_issuer_public_key(key.public_key()), critical=False) + .add_extension(x509.CRLNumber(number), critical=False) + .sign(key, hashes.SHA256()) + ) + + +def roa_content(prefix: ipaddress._BaseNetwork, max_length: int) -> bytes: + length = (prefix.prefixlen + 7) // 8 + raw = prefix.network_address.packed[:length] + address = seq(bit_string(raw, length * 8 - prefix.prefixlen), integer(max_length)) + family = seq(octet((1 if prefix.version == 4 else 2).to_bytes(2, "big")), seq(address)) + return seq(integer(TEST_ASN), seq(family)) + + +def aspa_content(customer_asn: int, providers: list[int]) -> bytes: + """RFC 9237/ASPA-profile test payload with explicit version 1.""" + return seq(context_zero(integer(1)), integer(customer_asn), seq(*(integer(provider) for provider in providers))) + + +def manifest_content(number: int, files: list[tuple[str, bytes]]) -> bytes: + entries = [ + seq(tlv(0x16, name.encode("ascii")), bit_string(hashlib.sha256(content).digest())) + for name, content in files + ] + return seq( + integer(number), + generalized_time(VALID_FROM + timedelta(days=30, seconds=number)), + generalized_time(VALID_TO - timedelta(days=30)), + oid(OID_SHA256), + seq(*entries), + ) + + +def write_cms(content: bytes, content_type: str, signer_cert: x509.Certificate, signer_key: rsa.RSAPrivateKey, output: Path, work: Path) -> None: + content_path = work / f"{output.name}.content.der" + signer_path = work / f"{output.name}.signer.pem" + key_path = work / f"{output.name}.key.pem" + write(content_path, content) + write(signer_path, pem_cert(signer_cert)) + write_key(key_path, signer_key) + subprocess.run( + [ + "openssl", "cms", "-sign", "-binary", "-in", str(content_path), + "-signer", str(signer_path), "-inkey", str(key_path), + "-outform", "DER", "-nodetach", "-nosmimecap", "-keyid", "-md", "sha256", + "-econtent_type", content_type, "-out", str(output), + ], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.PIPE, + ) + + +def make_roa( + name: str, + prefix, + max_length: int, + not_before: datetime, + not_after: datetime, + child_name, + child_key, + common: Path, + work: Path, + certificate_prefix=None, +) -> None: + key = rsa.generate_private_key(public_exponent=65537, key_size=2048) + cert = sign_cert( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, f"Local Test EE {name}")]), + child_name, + key.public_key(), + child_key, + x509.random_serial_number(), + False, + key, + child_key, + ip_ext=ip_resources([certificate_prefix or prefix]), + signed_object_uri=f"{CHILD_REPO_URI}{name}", + crl_uri=f"{CHILD_REPO_URI}child.crl", + ca_issuer_uri=f"{CHILD_REPO_URI}child.cer", + not_before=not_before, + not_after=not_after, + ) + write_cms(roa_content(prefix, max_length), OID_ROA_ECONTENT, cert, key, common / name, work) + + +def make_aspa(name: str, child_name, child_key, common: Path, work: Path) -> None: + key = rsa.generate_private_key(public_exponent=65537, key_size=2048) + cert = sign_cert( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, f"Local Test EE {name}")]), + child_name, + key.public_key(), + child_key, + x509.random_serial_number(), + False, + key, + child_key, + as_ext=as_resources([TEST_ASN]), + signed_object_uri=f"{CHILD_REPO_URI}{name}", + crl_uri=f"{CHILD_REPO_URI}child.crl", + ca_issuer_uri=f"{CHILD_REPO_URI}child.cer", + not_before=VALID_FROM + timedelta(days=31), + not_after=VALID_TO - timedelta(days=31), + ) + write_cms(aspa_content(TEST_ASN, [64497, 64498]), OID_ASPA_ECONTENT, cert, key, common / name, work) + + +def make_manifest(name: str, number: int, files: list[tuple[str, bytes]], child_name, child_key, common: Path, work: Path) -> bytes: + key = rsa.generate_private_key(public_exponent=65537, key_size=2048) + cert = sign_cert( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, f"Local Test Manifest EE {name}")]), + child_name, + key.public_key(), + child_key, + x509.random_serial_number(), + False, + key, + child_key, + ip_ext=ip_inherit(), + as_ext=as_resources(None), + signed_object_uri=CHILD_MFT_URI if name != "root" else ROOT_MFT_URI, + crl_uri=f"{CHILD_REPO_URI}child.crl" if name != "root" else f"{ROOT_REPO_URI}root.crl", + ca_issuer_uri=f"{CHILD_REPO_URI}child.cer" if name != "root" else f"{ROOT_REPO_URI}ta.cer", + ) + output = common / f"{name}.mft" + write_cms(manifest_content(number, files), OID_MANIFEST_ECONTENT, cert, key, output, work) + return output.read_bytes() + + +def publish(uri: str, path: Path) -> str: + encoded = base64.b64encode(path.read_bytes()).decode("ascii") + return f' {encoded}\n' + + +def snapshot(case_root: Path, names: list[str], serial: int, extra_root_names: list[str] | None = None) -> bytes: + repository = case_root / "repository" + objects = [ + (f"{ROOT_REPO_URI}root.crl", repository / "root/root.crl"), + (f"{ROOT_REPO_URI}child.cer", repository / "root/child.cer"), + *[ + (f"{ROOT_REPO_URI}{name}", repository / f"root/{name}") + for name in extra_root_names or [] + ], + (f"{CHILD_REPO_URI}child.crl", repository / "child/child.crl"), + *[(f"{CHILD_REPO_URI}{name}", repository / f"child/{name}") for name in names], + (ROOT_MFT_URI, repository / "root/root.mft"), + (CHILD_MFT_URI, repository / "child/child.mft"), + ] + body = "".join(publish(uri, path) for uri, path in objects) + return ( + # RFC 8182 carries the protocol version on every RRDP XML document, + # not just on notification.xml. Keeping the fixture strict here + # prevents a snapshot-only path from hiding a parser regression. + f'\n' + f"{body}\n" + ).encode() + + +def notification( + serial: int, + snap: bytes, + delta: bytes | None, + snapshot_uri: str | None = None, +) -> bytes: + lines = [ + f'', + f' ', + ] + if delta is not None: + lines.append( + f' ' + ) + return ("\n".join(lines) + "\n\n").encode() + + +def corrupt_first_hash(xml: bytes) -> bytes: + marker = b'hash="' + start = xml.index(marker) + len(marker) + end = xml.index(b'"', start) + value = bytearray(xml[start:end]) + value[0] = ord("0") if value[0] != ord("0") else ord("1") + return xml[:start] + bytes(value) + xml[end:] + + +def delta(add_name: str, add_path: Path, new_mft: Path, old_mft: bytes) -> bytes: + body = publish(CHILD_MFT_URI, new_mft).replace('\n{body}\n'.encode() + + +def build_case( + output: Path, + common: Path, + tal: bytes, + ta: bytes, + name: str, + number: int, + names: list[str], + child_mft: bytes, + change: bytes | None = None, + root_mft: bytes | None = None, + extra_root_objects: list[tuple[str, bytes]] | None = None, + notification_hash_mismatch: bool = False, + snapshot_uri: str | None = None, +) -> None: + case_root = output / "cases" / name + repo = case_root / "repository" + (repo / "root").mkdir(parents=True, exist_ok=True) + (repo / "child").mkdir(parents=True, exist_ok=True) + shutil.copy2(common / "ta.cer", repo / "ta.cer") + for item in ("root.crl", "child.cer"): + shutil.copy2(common / item, repo / "root" / item) + (repo / "root/root.mft").write_bytes(root_mft if root_mft is not None else (common / "root.mft").read_bytes()) + for item, content in extra_root_objects or []: + (repo / "root" / item).write_bytes(content) + for item in ( + "child.crl", "valid.roa", "valid2.roa", "valid.asa", "expired.roa", "max-length.roa", + "max-length-invalid.roa", "roa-prefix-outside.roa", "out-of-resource.roa", "bad-format.roa", + ): + if (common / item).exists(): + shutil.copy2(common / item, repo / "child" / item) + (repo / "child/child.mft").write_bytes(child_mft) + snap = snapshot(case_root, names, number, [item for item, _ in extra_root_objects or []]) + (case_root / "http/rrdp").mkdir(parents=True, exist_ok=True) + (case_root / "http/tal").mkdir(parents=True, exist_ok=True) + (case_root / "http/ta").mkdir(parents=True, exist_ok=True) + (case_root / "foreign-http/rrdp").mkdir(parents=True, exist_ok=True) + (case_root / "http/rrdp/snapshot.xml").write_bytes(snap) + # A complete copy is deliberately made available on the foreign service + # for the cross-origin case. A conformant RP must reject its URI before + # attempting this request; the service-side request log proves that. + (case_root / "foreign-http/rrdp/snapshot.xml").write_bytes(snap) + (case_root / "foreign-requests.log").write_text("", encoding="utf-8") + notification_xml = notification(number, snap, change, snapshot_uri=snapshot_uri) + if notification_hash_mismatch: + notification_xml = corrupt_first_hash(notification_xml) + (case_root / "http/rrdp/notification.xml").write_bytes(notification_xml) + if change is not None: + (case_root / "http/rrdp/delta-2.xml").write_bytes(change) + (case_root / "http/tal/custom.tal").write_bytes(tal) + (case_root / "http/ta/custom-ta.cer").write_bytes(ta) + (case_root / "CASE-MANIFEST.txt").write_text( + f"case={name}\nserial={number}\nobjects={','.join(names)}\nsnapshot_sha256={hashlib.sha256(snap).hexdigest()}\n", + encoding="utf-8", + ) + + +def make_https_certificates(output: Path) -> None: + ca_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) + ca_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "Local Test RRDP CA")]) + ca_ski = x509.SubjectKeyIdentifier.from_public_key(ca_key.public_key()) + ca_cert = ( + x509.CertificateBuilder() + .subject_name(ca_name).issuer_name(ca_name).public_key(ca_key.public_key()) + .serial_number(x509.random_serial_number()).not_valid_before(VALID_FROM).not_valid_after(VALID_TO) + .add_extension(x509.BasicConstraints(ca=True, path_length=1), critical=True) + .add_extension(x509.KeyUsage(False, False, False, False, False, True, True, False, False), critical=True) + .add_extension(ca_ski, critical=False) + .add_extension(x509.AuthorityKeyIdentifier.from_issuer_public_key(ca_key.public_key()), critical=False) + .sign(ca_key, hashes.SHA256()) + ) + server_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) + server_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, RRDP_HOST)]) + try: + server_host = x509.IPAddress(ipaddress.ip_address(RRDP_HOST)) + except ValueError: + server_host = x509.DNSName(RRDP_HOST) + san_entries = [server_host, x509.DNSName(FOREIGN_RRDP_HOST)] + if RRDP_HOST != "localhost": + san_entries.append(x509.DNSName("localhost")) + server_cert = ( + x509.CertificateBuilder() + .subject_name(server_name).issuer_name(ca_cert.subject).public_key(server_key.public_key()) + .serial_number(x509.random_serial_number()).not_valid_before(VALID_FROM).not_valid_after(VALID_TO) + .add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True) + .add_extension(x509.KeyUsage(True, False, False, False, False, False, False, False, False), critical=True) + .add_extension(x509.SubjectAlternativeName(san_entries), critical=False) + .add_extension(x509.SubjectKeyIdentifier.from_public_key(server_key.public_key()), critical=False) + .add_extension(x509.AuthorityKeyIdentifier.from_issuer_public_key(ca_key.public_key()), critical=False) + .sign(ca_key, hashes.SHA256()) + ) + write(output / "certs/rrdp-ca.pem", pem_cert(ca_cert)) + write_key(output / "certs/rrdp-ca.key", ca_key) + write(output / "certs/rrdp-server.pem", pem_cert(server_cert)) + write_key(output / "certs/rrdp-server.key", server_key) + + +def main() -> None: + global RRDP_HOST, FOREIGN_RRDP_HOST, RRDP_PORT, RSYNC_HOST, RSYNC_PORT + global TAL_URI, TAL_HTTPS_URI, RRDP_URI + global ROOT_REPO_URI, ROOT_MFT_URI, CHILD_REPO_URI, CHILD_MFT_URI + parser = argparse.ArgumentParser() + parser.add_argument("--output", type=Path, required=True) + parser.add_argument( + "--rrdp-host", + default=RRDP_HOST, + help="host embedded in RRDP URIs and the HTTPS server certificate SAN " + "(default: rpki-local-rrdp, the compose service name)", + ) + parser.add_argument( + "--rrdp-port", + type=int, + default=RRDP_PORT, + help="port embedded in RRDP/HTTPS URIs (default: 8443)", + ) + parser.add_argument( + "--foreign-rrdp-host", + default=FOREIGN_RRDP_HOST, + help="separate HTTPS service host used only by the cross-origin RRDP test case", + ) + parser.add_argument( + "--rsync-host", + default=RSYNC_HOST, + help="host embedded in the TAL rsync URI and certificate SIA rsync URIs " + "(default: rpki-local-rsync, the compose service name)", + ) + parser.add_argument( + "--rsync-port", + type=int, + default=RSYNC_PORT, + help="port embedded in rsync URIs (default: 873)", + ) + args = parser.parse_args() + RRDP_HOST = args.rrdp_host + FOREIGN_RRDP_HOST = args.foreign_rrdp_host + RRDP_PORT = args.rrdp_port + RSYNC_HOST = args.rsync_host + RSYNC_PORT = args.rsync_port + TAL_URI = f"rsync://{RSYNC_HOST}:{RSYNC_PORT}/custom/ta.cer" + TAL_HTTPS_URI = f"https://{RRDP_HOST}:{RRDP_PORT}/ta/custom-ta.cer" + RRDP_URI = f"https://{RRDP_HOST}:{RRDP_PORT}/rrdp/notification.xml" + ROOT_REPO_URI = f"rsync://{RSYNC_HOST}:{RSYNC_PORT}/custom/root/" + ROOT_MFT_URI = f"{ROOT_REPO_URI}root.mft" + CHILD_REPO_URI = f"rsync://{RSYNC_HOST}:{RSYNC_PORT}/custom/child/" + CHILD_MFT_URI = f"{CHILD_REPO_URI}child.mft" + output = args.output.resolve() + if output.exists() and any(output.iterdir()): + raise SystemExit("fixture output must be new or empty") + for directory in ("tal", "ta", "certs", "services", "keys"): + (output / directory).mkdir(parents=True) + + root_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) + child_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) + root_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "Local Test RPKI Root")]) + child_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "Local Test RPKI Child")]) + root_cert = sign_cert( + root_name, root_name, root_key.public_key(), root_key, 0x14700001, True, root_key, + ip_ext=ip_resources([CHILD_PREFIX]), + as_ext=as_resources([TEST_ASN]), ca_repository=ROOT_REPO_URI, manifest_uri=ROOT_MFT_URI, + notify_uri=RRDP_URI, + ) + child_cert = sign_cert( + child_name, root_name, child_key.public_key(), root_key, 0x14700002, True, child_key, root_key, + ip_ext=ip_resources([CHILD_PREFIX]), as_ext=as_resources([TEST_ASN]), + ca_repository=CHILD_REPO_URI, manifest_uri=CHILD_MFT_URI, notify_uri=RRDP_URI, + crl_uri=f"{ROOT_REPO_URI}root.crl", ca_issuer_uri=f"{ROOT_REPO_URI}child.cer", + ) + bad_child_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "Local Test RPKI Overclaiming Child")]) + bad_child_repo_uri = f"rsync://{RSYNC_HOST}:{RSYNC_PORT}/custom/bad-child/" + bad_child_cert = sign_cert( + bad_child_name, root_name, child_key.public_key(), root_key, 0x14700003, True, child_key, root_key, + ip_ext=ip_resources([OUTSIDE_PREFIX]), as_ext=as_resources([TEST_ASN]), + ca_repository=bad_child_repo_uri, manifest_uri=f"{bad_child_repo_uri}bad-child.mft", notify_uri=RRDP_URI, + crl_uri=f"{ROOT_REPO_URI}root.crl", ca_issuer_uri=f"{ROOT_REPO_URI}child.cer", + ) + root_crl = make_crl(root_name, root_key, 1) + child_crl = make_crl(child_name, child_key, 1) + tal = ( + f"{TAL_URI}\n{TAL_HTTPS_URI}\n\n" + + base64.b64encode(root_key.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)).decode() + + "\n" + ).encode() + write(output / "tal/custom.tal", tal) + write(output / "ta/custom-ta.cer", root_cert.public_bytes(Encoding.DER)) + with tempfile.TemporaryDirectory(prefix="custom-rpki-cms-") as temp: + work = Path(temp) + common = output / ".common" + common.mkdir() + write(common / "ta.cer", root_cert.public_bytes(Encoding.DER)) + write(common / "child.cer", child_cert.public_bytes(Encoding.DER)) + write(common / "bad-child.cer", bad_child_cert.public_bytes(Encoding.DER)) + write(common / "root.crl", root_crl.public_bytes(Encoding.DER)) + write(common / "child.crl", child_crl.public_bytes(Encoding.DER)) + for name, prefix, max_length, before, after, certificate_prefix in ( + ("valid.roa", CHILD_PREFIX, 24, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), None), + ("valid2.roa", SECOND_PREFIX, 25, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), None), + ("expired.roa", CHILD_PREFIX, 24, VALID_FROM - timedelta(days=30), VALID_FROM - timedelta(days=1), None), + ("max-length.roa", CHILD_PREFIX, 25, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), None), + ("max-length-invalid.roa", CHILD_PREFIX, 33, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), None), + ("roa-prefix-outside.roa", OUTSIDE_PREFIX, 24, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), CHILD_PREFIX), + ("out-of-resource.roa", OUTSIDE_PREFIX, 24, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), None), + ): + make_roa( + name, prefix, max_length, before, after, child_name, child_key, common, work, + certificate_prefix=certificate_prefix, + ) + make_aspa("valid.asa", child_name, child_key, common, work) + bad = bytearray((common / "valid.roa").read_bytes()) + bad[-1] ^= 1 + write(common / "bad-format.roa", bytes(bad)) + + root_mft = make_manifest( + "root", 1, [ + ("root.crl", (common / "root.crl").read_bytes()), + ("child.cer", (common / "child.cer").read_bytes()), + ], root_name, root_key, common, work, + ) + write(common / "root.mft", root_mft) + bad_root_mft = make_manifest( + "root", 1, [ + ("root.crl", (common / "root.crl").read_bytes()), + ("child.cer", (common / "child.cer").read_bytes()), + ("bad-child.cer", (common / "bad-child.cer").read_bytes()), + ], root_name, root_key, common, work, + ) + write(common / "root.mft", root_mft) + case_specs = ( + ("baseline-v1", 1, ["child.crl", "valid.roa", "valid.asa"]), + ("sync-hash-mismatch", 1, ["child.crl", "valid.roa", "valid.asa"]), + ("rrdp-cross-origin-snapshot", 1, ["child.crl", "valid.roa", "valid.asa"]), + ("baseline-v2", 2, ["child.crl", "valid.roa", "valid2.roa", "valid.asa"]), + ("validation-expired", 1, ["child.crl", "expired.roa", "valid.asa"]), + ("validation-max-length", 1, ["child.crl", "max-length.roa", "valid.asa"]), + ("validation-max-length-invalid", 1, ["child.crl", "max-length-invalid.roa", "valid.asa"]), + ("validation-roa-prefix-outside", 1, ["child.crl", "roa-prefix-outside.roa", "valid.asa"]), + ("validation-out-of-resource", 1, ["child.crl", "out-of-resource.roa", "valid.asa"]), + ("validation-ca-over-resource", 1, ["child.crl", "valid.roa", "valid.asa"]), + ("validation-nonstandard", 1, ["child.crl", "bad-format.roa", "valid.asa"]), + ) + child_mfts = {} + for name, number, names in case_specs: + child_mfts[name] = make_manifest( + name, number, [(item, (common / item).read_bytes()) for item in names], + child_name, child_key, common, work, + ) + for name, number, names in case_specs: + change = None + if name == "baseline-v2": + change = delta("valid2.roa", common / "valid2.roa", common / "baseline-v2.mft", child_mfts["baseline-v1"]) + build_case( + output, common, tal, root_cert.public_bytes(Encoding.DER), name, number, names, + child_mfts[name], change, + root_mft=bad_root_mft if name == "validation-ca-over-resource" else None, + extra_root_objects=[("bad-child.cer", (common / "bad-child.cer").read_bytes())] + if name == "validation-ca-over-resource" else None, + notification_hash_mismatch=name == "sync-hash-mismatch", + snapshot_uri=f"https://{FOREIGN_RRDP_HOST}:{RRDP_PORT}/rrdp/snapshot.xml" + if name == "rrdp-cross-origin-snapshot" else None, + ) + write_key(output / "keys/root.key", root_key) + write_key(output / "keys/child.key", child_key) + make_https_certificates(output) + shutil.rmtree(output / ".common") + (output / "FIXTURE-MANIFEST.txt").write_text( + "fixture_schema_version=1\nfixture_kind=stack-local-test-rpki-repo\n" + f"tal_uri={TAL_URI}\ntal_https_uri={TAL_HTTPS_URI}\nrrdp_uri={RRDP_URI}\nrsync_module=custom\n" + f"rrdp_host={RRDP_HOST}\nforeign_rrdp_host={FOREIGN_RRDP_HOST}\nrrdp_port={RRDP_PORT}\nrsync_host={RSYNC_HOST}\nrsync_port={RSYNC_PORT}\n" + "cases=baseline-v1,sync-hash-mismatch,rrdp-cross-origin-snapshot,baseline-v2,validation-expired,validation-max-length,validation-max-length-invalid," + "validation-roa-prefix-outside,validation-out-of-resource,validation-ca-over-resource,validation-nonstandard\n", + encoding="utf-8", + ) + print(f"generated custom fixture: {output}") + + +if __name__ == "__main__": + main() diff --git a/tests/support/serve_repository.py b/tests/support/serve_repository.py new file mode 100644 index 0000000..cc4ce7d --- /dev/null +++ b/tests/support/serve_repository.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +"""HTTPS fixture server, with request log for forbidden-origin assertions.""" +import functools +import http.server +import ssl +import sys + +root, port, cert, key = sys.argv[1:] +handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory=root) +server = http.server.ThreadingHTTPServer(('127.0.0.1', int(port)), handler) +context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) +context.load_cert_chain(cert, key) +server.socket = context.wrap_socket(server.socket, server_side=True) +print('ready', flush=True) +server.serve_forever() diff --git a/tests/support/test_daemon.py b/tests/support/test_daemon.py new file mode 100644 index 0000000..b9bf59d --- /dev/null +++ b/tests/support/test_daemon.py @@ -0,0 +1,158 @@ +"""Real CLI regression: HTTPS snapshot/delta, restart, interval, lock and signals.""" +import json +import os +from pathlib import Path +import signal +import socket +import subprocess +import sys +import tempfile +import time + + +def read(path): + return json.loads(path.read_text()) + + +def wait_for(check, label, timeout=30): + start = time.monotonic() + while time.monotonic() - start < timeout: + if check(): + return + time.sleep(0.05) + raise AssertionError(f"timed out waiting for {label}") + + +def state_is(root, state): + path = root / "daemon-status.json" + return path.exists() and read(path)["state"] == state + + +def run(binary): + support = Path(__file__).resolve().parent + with tempfile.TemporaryDirectory() as tmp: + base = Path(tmp) + fixture = base / "fixture" + with socket.socket() as sock: + sock.bind(("127.0.0.1", 0)) + port = sock.getsockname()[1] + subprocess.run([sys.executable, str(support / "generate_repository.py"), + "--rrdp-host", "127.0.0.1", "--rrdp-port", str(port), + # Key generation is CPU-heavy under instrumented CI builds. + "--output", str(fixture)], check=True, timeout=120) + active = fixture / "active" + active.symlink_to("cases/baseline-v1/http") + server = subprocess.Popen([sys.executable, str(support / "serve_repository.py"), + str(active), str(port), str(fixture / "certs/rrdp-server.pem"), + str(fixture / "certs/rrdp-server.key")], + stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True) + processes = [] + logs = [] + try: + assert server.stdout.readline().strip() == "ready" + child_args = ["--tal", str(fixture / "tal/custom.tal"), "--ta", str(fixture / "ta/custom-ta.cer"), + "--http-root-cert", str(fixture / "certs/rrdp-ca.pem"), + "--http-timeout-secs", "2", "--log-format", "json"] + + def launch(root, options, inputs=None): + log = (base / f"controller-{len(logs)}.log").open("w") + logs.append(log) + proc = subprocess.Popen([binary, "daemon", "--state-root", str(root), *options, + "--", *(child_args if inputs is None else inputs)], + stdout=log, stderr=log) + processes.append(proc) + return proc + + root = base / "normal" + proc = launch(root, ["--interval-secs", "2", "--max-runs", "2", "--retain-runs", "2"]) + wait_for(lambda: state_is(root, "sleeping"), "snapshot completion") + first = read(root / "runs/run_000001/run-summary.json") + assert first["status"] == "success" and first["sync_mode"] == "snapshot", first + assert first["summary"]["vrps"] == 1, first + # Single controller even in the interval when RocksDB itself is closed. + rival = launch(root, ["--max-runs", "1"]) + assert rival.wait(timeout=10) == 2 + active.unlink() + active.symlink_to("cases/baseline-v2/http") + assert proc.wait(timeout=30) == 0 + second = read(root / "runs/run_000002/run-summary.json") + assert second["status"] == "success" and second["sync_mode"] == "delta", second + assert second["summary"]["vrps"] == 2, second + timing = read(root / "runs/run_000002/stage-timing.json") + assert timing["analysis_counts"]["rrdp_delta_ops_applied_total"] > 0 + from datetime import datetime + gap = (datetime.fromisoformat(second["started_at"].replace("Z", "+00:00")) - + datetime.fromisoformat(first["finished_at"].replace("Z", "+00:00"))).total_seconds() + assert gap >= 1.9, gap + for seq in (1, 2): + out = root / f"runs/run_{seq:06}" + assert len((out / "vrps.csv").read_text().splitlines()) - 1 == seq + subprocess.run(["openssl", "asn1parse", "-inform", "DER", "-in", + str(out / "result.ccr"), "-noout"], check=True) + # Restart appends sequence and keeps protocol state; retention only drops completed runs. + (root / "runs/operator-notes").mkdir() + (root / "runs/run_000003").mkdir() # Simulated interrupted run evidence. + restart = launch(root, ["--interval-secs", "0", "--max-runs", "1", "--retain-runs", "1"]) + assert restart.wait(timeout=30) == 0 + fourth = read(root / "runs/run_000004/run-summary.json") + assert fourth["sync_mode"] == "auto" and fourth["summary"]["vrps"] == 2 + assert not (root / "runs/run_000001").exists() + assert (root / "runs/operator-notes").is_dir() and (root / "runs/run_000003").is_dir() + + sleeper = launch(root, ["--interval-secs", "600"]) + wait_for(lambda: state_is(root, "sleeping"), "daemon sleep") + sleeper.send_signal(signal.SIGTERM) + assert sleeper.wait(timeout=5) == 0 + assert state_is(root, "exited") + + # A real validator failure is recorded, retried, and returned as nonzero at max-runs. + failed = base / "failed" + bad_inputs = ["--tal", str(base / "missing.tal"), "--ta", str(base / "missing.cer")] + failure = launch(failed, ["--max-runs", "2", "--interval-secs", "0"], bad_inputs) + assert failure.wait(timeout=20) == 2 + for seq in (1, 2): + assert read(failed / f"runs/run_{seq:06}/run-summary.json")["status"] == "failed" + + # Suspend the HTTPS server so the actual validator cannot complete a request. + server.send_signal(signal.SIGSTOP) + hung = base / "timeout" + timeout = launch(hung, ["--max-runs", "1", "--run-timeout-secs", "1"]) + assert timeout.wait(timeout=10) == 2 + assert read(hung / "runs/run_000001/run-summary.json")["error"] == "timeout" + interrupted = base / "interrupted" + active_proc = launch(interrupted, ["--shutdown-grace-secs", "0"]) + wait_for(lambda: state_is(interrupted, "running"), "active validator") + time.sleep(0.2) + active_proc.send_signal(signal.SIGTERM) + assert active_proc.wait(timeout=10) == 2 + assert read(interrupted / "runs/run_000001/run-summary.json")["error"] == "interrupted" + server.send_signal(signal.SIGCONT) + recovered = launch(interrupted, ["--max-runs", "1", "--interval-secs", "0"]) + assert recovered.wait(timeout=30) == 0 + resumed = read(interrupted / "runs/run_000002/run-summary.json") + assert resumed["status"] == "success" and resumed["summary"]["vrps"] == 2 + assert resumed["sync_mode"] in ("auto", "snapshot") + print("Real daemon E2E passed: snapshot/delta/restart/interval/retention/lock/failure/timeout/SIGTERM") + except BaseException: + for log in logs: + log.flush() + print(Path(log.name).read_text(), file=sys.stderr) + raise + finally: + server.send_signal(signal.SIGCONT) + for proc in processes: + if proc.poll() is None: + proc.terminate() + try: + proc.wait(timeout=35) + except subprocess.TimeoutExpired: + proc.kill() + proc.wait() + server.terminate() + server.wait(timeout=5) + for log in logs: + log.close() + + +if __name__ == "__main__": + run(str(Path(sys.argv[1]).resolve())) diff --git a/tests/synthetic_docker_e2e.sh b/tests/synthetic_docker_e2e.sh new file mode 100755 index 0000000..6df5297 --- /dev/null +++ b/tests/synthetic_docker_e2e.sh @@ -0,0 +1,139 @@ +#!/usr/bin/env bash +# Self-contained Docker deployment regression. Requires Linux Docker because +# the validator uses the host network to reach the ephemeral HTTPS RRDP server. +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +work_root="$(mktemp -d)" +server_pid="" +daemon_container="" +cleanup() { + [[ -n "$daemon_container" ]] && docker stop --time 5 "$daemon_container" >/dev/null 2>&1 || true + [[ -n "$server_pid" ]] && kill "$server_pid" 2>/dev/null || true + [[ -n "$server_pid" ]] && wait "$server_pid" 2>/dev/null || true + rm -rf "$work_root" +} +trap cleanup EXIT + +port="$(python3 - <<'PY' +import socket +with socket.socket() as sock: + sock.bind(("127.0.0.1", 0)) + print(sock.getsockname()[1]) +PY +)" +fixture="$work_root/fixture" +python3 "$repo_root/tests/support/generate_repository.py" \ + --rrdp-host 127.0.0.1 --rrdp-port "$port" --output "$fixture" +ln -s "cases/baseline-v1/http" "$fixture/active" +mkdir -p "$work_root/output-snapshot" "$work_root/output-delta" "$work_root/state" + +PYTHONUNBUFFERED=1 python3 "$repo_root/tests/support/serve_repository.py" \ + "$fixture/active" "$port" "$fixture/certs/rrdp-server.pem" "$fixture/certs/rrdp-server.key" \ + >"$work_root/server.log" 2>&1 & +server_pid="$!" +for _ in $(seq 1 50); do + rg -qx 'ready' "$work_root/server.log" && break + sleep 0.1 +done +rg -qx 'ready' "$work_root/server.log" + +docker build -f "$repo_root/docker/Dockerfile" -t panda-rpki:synthetic-e2e "$repo_root" +run_validator() { + local out="$1" + local mode="$2" + docker run --rm --network host --user "$(id -u):$(id -g)" \ + -v "$fixture:/fixture:ro" \ + -v "$work_root/state:/state" \ + -v "$out:/output" \ + panda-rpki:synthetic-e2e validate \ + --tal /fixture/tal/custom.tal --ta /fixture/ta/custom-ta.cer \ + --http-root-cert /fixture/certs/rrdp-ca.pem \ + --http-timeout-secs 5 \ + --parallel-max-repo-sync-workers-global 2 \ + --parallel-phase2-object-workers 2 \ + --rrdp-state-dir /state --rrdp-sync-mode "$mode" \ + --ccr-out /output/result.ccr --out /output --log-level info --log-format json +} + +run_validator "$work_root/output-snapshot" snapshot +rm "$fixture/active" +ln -s "cases/baseline-v2/http" "$fixture/active" +run_validator "$work_root/output-delta" delta + +for output in "$work_root/output-snapshot" "$work_root/output-delta"; do + test -s "$output/summary.json" + test -s "$output/vrps.csv" + test -s "$output/result.ccr" + openssl asn1parse -inform DER -in "$output/result.ccr" -noout +done +test -s "$work_root/state/repository-db/CURRENT" +python3 - "$work_root/output-snapshot" "$work_root/output-delta" <<'PY' +import json +import pathlib +import sys + +snapshot, delta = (pathlib.Path(item) for item in sys.argv[1:]) +first = json.loads((snapshot / "summary.json").read_text(encoding="utf-8")) +second = json.loads((delta / "summary.json").read_text(encoding="utf-8")) +assert first["vrps"] == 1, first +assert second["vrps"] == 2, second +assert len((snapshot / "vaps.csv").read_text(encoding="utf-8").splitlines()) == 2 +assert len((delta / "vaps.csv").read_text(encoding="utf-8").splitlines()) == 2 +timing = json.loads((delta / "stage-timing.json").read_text(encoding="utf-8")) +assert timing["analysis_counts"]["rrdp_delta_ops_applied_total"] > 0, timing +PY + +echo "Synthetic Docker snapshot+delta E2E passed" + +# Exercise the actual public daemon in the image with a fresh protocol root. +mkdir "$work_root/daemon" +rm "$fixture/active" +ln -s "cases/baseline-v1/http" "$fixture/active" +daemon_container="panda-rpki-daemon-$(basename "$work_root")" +docker run --rm --name "$daemon_container" --network host --read-only --tmpfs /tmp \ + --user "$(id -u):$(id -g)" \ + -v "$fixture:/fixture:ro" -v "$work_root/daemon:/data" \ + panda-rpki:synthetic-e2e daemon \ + --state-root /data --max-runs 3 --interval-secs 2 --retain-runs 3 -- \ + --tal /fixture/tal/custom.tal --ta /fixture/ta/custom-ta.cer \ + --http-root-cert /fixture/certs/rrdp-ca.pem --log-format json \ + >"$work_root/daemon-container.log" 2>&1 & +daemon_job=$! +python3 - "$work_root/daemon" "$fixture" <<'PY' +import json +import pathlib +import sys +import time +root, fixture = map(pathlib.Path, sys.argv[1:]) +for _ in range(400): + status = root / "daemon-status.json" + if status.exists() and json.loads(status.read_text())["state"] == "sleeping": + (fixture / "active").unlink() + (fixture / "active").symlink_to("cases/baseline-v2/http") + break + time.sleep(0.05) +else: + raise AssertionError("Docker daemon did not finish snapshot") +PY +wait "$daemon_job" +daemon_container="" +python3 - "$work_root/daemon" <<'PY' +import json +import pathlib +import subprocess +import sys +root = pathlib.Path(sys.argv[1]) +assert json.loads((root / "daemon-status.json").read_text())["runs_completed"] == 3 +for seq, mode, vrps in [(1, "snapshot", 1), (2, "delta", 2), (3, "delta", 2)]: + out = root / f"runs/run_{seq:06}" + run = json.loads((out / "run-summary.json").read_text()) + assert run["status"] == "success" and run["sync_mode"] == mode, run + assert run["summary"]["vrps"] == vrps, run + assert run["process_metrics"]["maxRssKb"] > 0, run + assert len((out / "vrps.csv").read_text().splitlines()) - 1 == vrps + subprocess.run(["openssl", "asn1parse", "-inform", "DER", "-in", str(out / "result.ccr"), "-noout"], check=True) +timing = json.loads((root / "runs/run_000002/stage-timing.json").read_text()) +assert timing["analysis_counts"]["rrdp_delta_ops_applied_total"] > 0 +print("Synthetic Docker daemon 3-run E2E passed") +PY