diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1515933 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +crates/libremetaverse/assets/avatar_lad.xml -whitespace diff --git a/.gitea/workflows/artifact-audit.yml b/.gitea/workflows/artifact-audit.yml new file mode 100644 index 0000000..1155148 --- /dev/null +++ b/.gitea/workflows/artifact-audit.yml @@ -0,0 +1,94 @@ +name: Native release artifact audit + +on: + push: + paths: + - ".gitea/workflows/artifact-audit.yml" + - "ci/artifact-policy.json" + - "ci/dependency-policy.json" + - "ci/provenance-policy.json" + - "tools/ci-matrix/**" + - "tools/install_openjpeg_2_5_4.sh" + - "docs/release-artifacts.md" + - "Cargo.toml" + - "Cargo.lock" + - "crates/**" + - "programs/**" + pull_request: + paths: + - ".gitea/workflows/artifact-audit.yml" + - "ci/artifact-policy.json" + - "ci/dependency-policy.json" + - "ci/provenance-policy.json" + - "tools/ci-matrix/**" + - "tools/install_openjpeg_2_5_4.sh" + - "docs/release-artifacts.md" + - "Cargo.toml" + - "Cargo.lock" + - "crates/**" + - "programs/**" + workflow_dispatch: + +env: + CARGO_BUILD_JOBS: 1 + CARGO_INCREMENTAL: 0 + FORCE_SKIA_BINARIES_DOWNLOAD: 1 + OPENJPEG_PREFIX: /tmp/metacrate-openjpeg-artifact-audit + PKG_CONFIG_PATH: /tmp/metacrate-openjpeg-artifact-audit/lib/pkgconfig + LD_LIBRARY_PATH: /tmp/metacrate-openjpeg-artifact-audit/lib + +jobs: + audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.97.1 + - name: Install documented native prerequisites + run: | + sudo apt-get update + sudo apt-get install --yes build-essential clang cmake curl ninja-build pkg-config python3 libfontconfig1-dev libfreetype6-dev libopus-dev + tools/install_openjpeg_2_5_4.sh "$OPENJPEG_PREFIX" + pkg-config --exact-version 2.5.4 libopenjp2 + pkg-config --modversion opus + - name: Record source identity without a shipped build helper + run: echo "METACRATE_RUST_COMMIT=$(git rev-parse --verify HEAD)" >>"$GITHUB_ENV" + - name: Fetch the locked dependency graph + run: cargo fetch --locked + - name: Create every release source package atomically + run: | + cargo package --locked --no-verify \ + -p libremetaverse-types \ + -p libremetaverse-structured-data \ + -p libremetaverse-imaging \ + -p libremetaverse-imaging-skia \ + -p libremetaverse-openjpeg \ + -p libremetaverse-opus \ + -p libremetaverse-prim-mesher \ + -p libremetaverse-lsl-tools \ + -p libremetaverse \ + -p libremetaverse-rendering-simple \ + -p libremetaverse-rendering-mesh-foundry \ + -p libremetaverse-rlv \ + -p libremetaverse-utilities \ + -p libremetaverse-voice-vivox \ + -p libremetaverse-voice-webrtc \ + -p libremetaverse-programs + - name: Build every shipped native executable + run: cargo build --locked --release -j 1 -p libremetaverse-programs --bins + - name: Inspect packages, binaries, links, and offline runtime behavior + run: | + cargo run --locked -p metacrate-ci-matrix -- artifact-audit \ + --artifact-dir target/release \ + --package-dir target/package \ + --evidence artifacts/release/artifact-audit.json + - name: Upload immutable audit evidence and source packages + if: always() + uses: actions/upload-artifact@v4 + with: + name: native-release-artifact-audit + path: | + artifacts/release/artifact-audit.json + target/package/*.crate + if-no-files-found: error diff --git a/Cargo.lock b/Cargo.lock index 13baae1..0491bcf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1071,6 +1071,17 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" +[[package]] +name = "goblin" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "983a6aafb3b12d4c41ea78d39e189af4298ce747353945ff5105b54a056e5cd9" +dependencies = [ + "log", + "plain", + "scroll", +] + [[package]] name = "group" version = "0.13.0" @@ -1773,9 +1784,12 @@ dependencies = [ name = "metacrate-ci-matrix" version = "0.0.1" dependencies = [ + "flate2", + "goblin", "serde", "serde_json", "sha2 0.11.0", + "tar", ] [[package]] @@ -2302,6 +2316,12 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "poly1305" version = "0.8.0" @@ -2777,6 +2797,26 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "scroll" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed76efe62313ab6610570951494bdaa81568026e0318eaa55f167de70eeea67d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "sctp-proto" version = "0.10.3" diff --git a/Cargo.toml b/Cargo.toml index b6dfc2a..eb8d74d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [workspace] resolver = "3" -exclude = ["crates/libremetaverse-openjpeg"] members = [ "crates/libremetaverse-types", "crates/libremetaverse-structured-data", "crates/libremetaverse-imaging", "crates/libremetaverse-imaging-skia", + "crates/libremetaverse-openjpeg", "crates/libremetaverse-prim-mesher", "crates/libremetaverse", "crates/libremetaverse-rendering-simple", @@ -29,7 +29,7 @@ version = "0.0.1" edition = "2024" rust-version = "1.96" license = "BSD-3-Clause" -repository = "https://github.com/cinderblocks/libremetaverse" +repository = "https://git.rfc1437.de/hugo/MetaCrate" [workspace.lints.rust] unsafe_code = "forbid" @@ -38,6 +38,15 @@ unsafe_code = "forbid" all = { level = "warn", priority = -1 } pedantic = { level = "warn", priority = -1 } +# The generated compatibility core is intentionally large. A bounded release +# profile keeps clean-machine packaging viable on the documented 8 GiB worker +# while still producing optimized, debug-free native artifacts. +[profile.release] +opt-level = 1 +debug = 0 +incremental = false +codegen-units = 256 + # The generated core API is intentionally very large; full release LTO-style # optimization can exceed small Gitea worker memory before diagnostic fixture # benchmarks run. This stable profile retains optimization without debug or diff --git a/README.md b/README.md index f01ebe2..a92a422 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,9 @@ offline-build, cache, and redistribution details are in the The validated Linux/MSRV/Windows/macOS target and feature inventory, clean profile runner, native prerequisite evidence, and manual platform boundaries are documented in the [release CI matrix](docs/release-ci-matrix.md). +The [native release artifact audit](docs/release-artifacts.md) documents the +clean-machine package build, binary/link inspection, offline runtime smokes, +and evidence proving that shipped artifacts have no CLR or bridge dependency. Deterministic task, socket, file, subscription, cancellation, and allocation baselines are documented in the [concurrency hardening guide](docs/concurrency-hardening.md). diff --git a/RUSTREWRITE.md b/RUSTREWRITE.md index 0af13c7..08e5a75 100644 --- a/RUSTREWRITE.md +++ b/RUSTREWRITE.md @@ -412,6 +412,7 @@ MSRV, features, and licenses at adoption time. | ZLogger / Microsoft logging | [`tracing` 0.1.44](https://crates.io/crates/tracing/0.1.44), [`tracing-subscriber` 0.3.23](https://crates.io/crates/tracing-subscriber/0.3.23) | Libraries emit spans/events; binaries choose formatting and filters. Never log credentials or capability tokens. | | rate limiting | [`governor` 0.10.4](https://crates.io/crates/governor/0.10.4) | Candidate for caps categories. First reproduce burst/refill/cancellation behavior with deterministic clock tests. | | compression and tar archives | [`flate2` 1.1.9](https://crates.io/crates/flate2/1.1.9), [`tar` 0.4.46](https://crates.io/crates/tar/0.4.46) | Enforce decompressed-size, path traversal, and entry-count limits on untrusted OAR data. | +| release artifact inspection | [`goblin` 0.10.5](https://crates.io/crates/goblin/0.10.5) | Parse ELF, PE, and Mach-O imports, symbols, and linked libraries without executing an artifact or relying on platform-only inspection tools. | | XML and URL | [`quick-xml` 0.41.0](https://crates.io/crates/quick-xml/0.41.0), [`url` 2.5.8](https://crates.io/crates/url/2.5.8), [`base64` 0.23.1](https://crates.io/crates/base64/0.23.1) | Streaming XML for LLSD/login; retain exact URL escaping behavior with fixtures. | | CoreJ2K 2.3.3.91 | system [OpenJPEG](https://www.openjpeg.org/) 2.5.4+ through the private `libremetaverse-openjpeg` adapter | Opt-in `jpeg2000` feature; checked-in minimal bindings avoid a libclang build dependency. Bounded memory streams and header validation isolate native code. Golden tests cover channel order, alpha, 16-bit precision, dimensions, discard levels, lossless/lossy modes, and malformed input. | | SkiaSharp 4.150.1 | [`skia-safe` 0.99.0](https://crates.io/crates/skia-safe/0.99.0) | Adopted behind the opt-in `skia` feature. Target-specific official binary-cache feature sets provide BMP/GIF/ICO/JPEG/PNG/WBMP/WebP decoding on macOS, Linux, and Windows; checked owned buffers keep Skia types out of the core image API. | @@ -443,7 +444,7 @@ The current adopted set is: | Wire data and parsing | `base64`, `serde`, `serde_json`, `roxmltree`, `regex`, `unicode-general-category` | | Identity, hashing, and platform facts | `uuid`, `getrandom`, `md-5`, `sha1`, `sha2`, `mac_address2`, `os_info` | | Archives, images, and audio | `flate2`, `tar`, `bcdec_rs`, `skia-safe`, `hound`, `vorbis_rs`, `str0m`, `cpal` | -| Code generation and program CLI | `syn`, `prettyplease`, `clap` | +| Code generation, program CLI, and artifact inspection | `syn`, `prettyplease`, `clap`, `goblin` | | Build and compatibility-test support | `pkg-config`, `vcpkg`, `stats_alloc` | System libopus is accessed only through the private `libremetaverse-opus` @@ -867,6 +868,13 @@ not part of the default smoke test. artifact/dependency audit proving there is no CLR, .NET assembly, subprocess, RPC, or LibreMetaverse foreign-interface dependency. +The native release artifact gate packages all 16 release/source crates as one +versioned set, rebuilds those extracted packages offline without the reference +checkout, parses every shipped ELF/PE/Mach-O program and its native imports, and +runs credential-free helper-trapped smoke modes for all ten executables. The +policy, clean-machine prerequisites, and reproducible commands are documented +in [`docs/release-artifacts.md`](docs/release-artifacts.md). + Each milestone ends with zero unreviewed generated shims in its owned modules, no weakened parity assertions, formatted/clippy-clean code, public Rust docs that cite the corresponding C# concept, and a refreshed completeness report diff --git a/api/DOCUMENTATION-COVERAGE.md b/api/DOCUMENTATION-COVERAGE.md index c3690cd..9f8ca11 100644 --- a/api/DOCUMENTATION-COVERAGE.md +++ b/api/DOCUMENTATION-COVERAGE.md @@ -9,6 +9,6 @@ Generated by `metacrate-ci-matrix documentation-report`; do not edit by hand. | Mapped public members | 30789 | 30789 | 100% | | Compiled Rust guide snippets | 5 | 4 minimum | pass | | Linked native programs | 9 | 9 | 100% | -| Checked local Markdown links | 69 | 69 | 100% | +| Checked local Markdown links | 84 | 84 | 100% | Every mapped item is tied to its exact C# documentation ID and to the ownership, asyncness, error, overload, mapping-kind, and Rust-signature decisions in [`RUST-MAPPING.tsv`](RUST-MAPPING.tsv). Public types are tied to the corresponding type mapping. The upstream source is pinned to [`2aa70bb68513b39795da5d13c88f31b86e85a3ba`](https://github.com/cinderblocks/libremetaverse/tree/2aa70bb68513b39795da5d13c88f31b86e85a3ba). diff --git a/ci/artifact-policy.json b/ci/artifact-policy.json new file mode 100644 index 0000000..a0f3b6a --- /dev/null +++ b/ci/artifact-policy.json @@ -0,0 +1,79 @@ +{ + "schema": 1, + "version": "0.0.1", + "release_packages": [ + "libremetaverse-types", + "libremetaverse-structured-data", + "libremetaverse-imaging", + "libremetaverse-imaging-skia", + "libremetaverse-openjpeg", + "libremetaverse-opus", + "libremetaverse-prim-mesher", + "libremetaverse-lsl-tools", + "libremetaverse", + "libremetaverse-rendering-simple", + "libremetaverse-rendering-mesh-foundry", + "libremetaverse-rlv", + "libremetaverse-utilities", + "libremetaverse-voice-vivox", + "libremetaverse-voice-webrtc", + "libremetaverse-programs" + ], + "binaries": [ + {"name":"inventory-explorer","services":["OpenSim login, capabilities, AIS, UDP when explicitly given credentials"]}, + {"name":"irc-gateway","services":["OpenSim and IRC TCP when explicitly given credentials and an IRC endpoint"]}, + {"name":"live-grid-smoke","services":["none in audit/fake mode; explicit OpenSim endpoint in live mode"]}, + {"name":"osd-inspector","services":["none; offline files and standard streams only"]}, + {"name":"packet-dump","services":["OpenSim login and UDP when explicitly given credentials"]}, + {"name":"prim-inspector","services":["OpenSim login, capabilities, and UDP when explicitly given credentials"]}, + {"name":"simple-bot","services":["OpenSim login, capabilities, and UDP when explicitly given credentials"]}, + {"name":"test-client","services":["OpenSim services selected by explicit login and commands"]}, + {"name":"vivox-test","services":["none in help mode; explicit external Vivox control service in live mode"]}, + {"name":"webrtc-test","services":["IPv4 loopback in fake mode; explicit OpenSim voice capabilities in live mode"]} + ], + "offline_smokes": [ + {"binary":"osd-inspector","args":["prim-to-osd"],"required_output":false}, + {"binary":"live-grid-smoke","args":["--audit-only"],"required_output":false}, + {"binary":"live-grid-smoke","args":["--fake","--evidence","{output}"],"required_output":true}, + {"binary":"webrtc-test","args":["--fake","--timeout-seconds","10"],"required_output":false} + ], + "allowed_native_ffi": [ + "crates/libremetaverse-openjpeg/src/ffi.rs", + "crates/libremetaverse-openjpeg/src/lib.rs", + "crates/libremetaverse-opus/src/lib.rs" + ], + "mirrored_inputs": [ + {"source":"codegen/inputs/avatar_lad.xml","package_path":"crates/libremetaverse/assets/avatar_lad.xml"} + ], + "forbidden_dependencies": [ + "coreclr", + "grpc", + "grpcio", + "hostfxr", + "jsonrpsee", + "mono", + "netcorehost", + "pythonnet", + "tarpc", + "thrift", + "tonic", + "xmlrpc" + ], + "forbidden_artifact_strings": [ + "coreclr", + "hostfxr", + "mscoree.dll", + "libmono", + "libremetaverse.dll", + "system.runtime.loader", + "assemblyloadcontext" + ], + "forbidden_source_strings": [ + "std::process::Command", + "tokio::process", + "Command::new(\"dotnet\")", + "Command::new(\"mono\")", + "libloading::", + "include_bytes!(\"LibreMetaverse.dll\")" + ] +} diff --git a/ci/dependency-policy.json b/ci/dependency-policy.json index 6993b9d..fe57ffb 100644 --- a/ci/dependency-policy.json +++ b/ci/dependency-policy.json @@ -9,10 +9,11 @@ { "name": "bcdec_rs", "versions": ["0.2.0"], "purpose": "Pure Rust BC6H and BC7 texture block decoding", "maintenance": "stable", "transitive_cost": "low", "native": false, "rewrite_anchor": "`bcdec_rs`" }, { "name": "clap", "versions": ["4.6.6"], "purpose": "Typed command-line parsing for shipped diagnostic programs", "maintenance": "active", "transitive_cost": "medium", "native": false, "rewrite_anchor": "`clap`" }, { "name": "cpal", "versions": ["0.18.1"], "purpose": "Opt-in cross-platform physical audio device access", "maintenance": "monitored-native", "transitive_cost": "high", "native": true, "rewrite_anchor": "`cpal`" }, - { "name": "flate2", "versions": ["1.1.9"], "purpose": "Bounded gzip and deflate asset/archive decoding", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`flate2`" }, + { "name": "flate2", "versions": ["1.1.9"], "purpose": "Bounded gzip and deflate asset/archive and release-package decoding", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`flate2`" }, { "name": "futures-channel", "versions": ["0.3.33"], "purpose": "One-shot compatibility callbacks without another runtime", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`futures-channel`" }, { "name": "futures-util", "versions": ["0.3.33"], "purpose": "Stream adaptation for HTTP capability bodies", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`futures-util`" }, { "name": "getrandom", "versions": ["0.4.3"], "purpose": "Operating-system entropy for protocol identifiers", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`getrandom`" }, + { "name": "goblin", "versions": ["0.10.5"], "purpose": "Cross-platform ELF, PE, and Mach-O release artifact inspection", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`goblin`" }, { "name": "hound", "versions": ["3.5.1"], "purpose": "Bounded PCM WAV parsing for WebRTC playback", "maintenance": "stable", "transitive_cost": "low", "native": false, "rewrite_anchor": "`hound`" }, { "name": "mac_address2", "versions": ["2.0.2"], "purpose": "Cross-platform machine identity compatibility input", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`mac_address2`" }, { "name": "md-5", "versions": ["0.10.6"], "purpose": "Legacy protocol checksum compatibility", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`md-5`" }, @@ -30,7 +31,7 @@ { "name": "stats_alloc", "versions": ["0.1.10"], "purpose": "Allocation-budget compatibility tests, concurrency leak audits, and reproducible performance evidence", "maintenance": "stable", "transitive_cost": "low", "native": false, "rewrite_anchor": "`stats_alloc`" }, { "name": "str0m", "versions": ["0.22.0"], "purpose": "Native Rust ICE, DTLS, SRTP, RTP, and SCTP WebRTC transport", "maintenance": "active", "transitive_cost": "high", "native": false, "rewrite_anchor": "`str0m`" }, { "name": "syn", "versions": ["2.0.119"], "purpose": "Syntax validation for generated Rust sources", "maintenance": "active", "transitive_cost": "medium", "native": false, "rewrite_anchor": "`syn`" }, - { "name": "tar", "versions": ["0.4.46"], "purpose": "Bounded OAR and asset archive traversal", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`tar`" }, + { "name": "tar", "versions": ["0.4.46"], "purpose": "Bounded OAR, asset, and release-package archive traversal", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`tar`" }, { "name": "tokio", "versions": ["1.53.1"], "purpose": "Shared asynchronous networking, timers, channels, and tasks", "maintenance": "active", "transitive_cost": "medium", "native": false, "rewrite_anchor": "`tokio`" }, { "name": "unicode-general-category", "versions": ["1.1.0"], "purpose": "Unicode category matching in the LSL lexer", "maintenance": "stable", "transitive_cost": "low", "native": false, "rewrite_anchor": "`unicode-general-category`" }, { "name": "uuid", "versions": ["1.24.0"], "purpose": "Random UUID generation behind protocol-compatible wrappers", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`uuid`" }, diff --git a/ci/evidence/artifact-audit.json b/ci/evidence/artifact-audit.json new file mode 100644 index 0000000..f35978f --- /dev/null +++ b/ci/evidence/artifact-audit.json @@ -0,0 +1,437 @@ +{ + "schema": 1, + "source_commit": "738fe3933eeb64d9081a53c71753e8d1e43c4d00", + "rustc": "rustc 1.97.1 (8bab26f4f 2026-07-14)", + "operating_system": "linux", + "architecture": "x86_64", + "cargo_graph_packages": 378, + "release_packages": [ + { + "name": "libremetaverse-types", + "version": "0.0.1", + "archive": "libremetaverse-types-0.0.1.crate", + "sha256": "18d44c43c3799938da5affd119519f20a9f380e626f6682d8ce42ccfdba2de8f", + "files": 19, + "unpacked_bytes": 648009 + }, + { + "name": "libremetaverse-structured-data", + "version": "0.0.1", + "archive": "libremetaverse-structured-data-0.0.1.crate", + "sha256": "b482a76b2b785085da673df29a8dd7efe4d278cd69a8d7e5d822dfc407057f2a", + "files": 14, + "unpacked_bytes": 339585 + }, + { + "name": "libremetaverse-imaging", + "version": "0.0.1", + "archive": "libremetaverse-imaging-0.0.1.crate", + "sha256": "bd797ee3dacf4475b078cc6e881b4db5419f01fe7267453bb140054051fd08a2", + "files": 10, + "unpacked_bytes": 87617 + }, + { + "name": "libremetaverse-imaging-skia", + "version": "0.0.1", + "archive": "libremetaverse-imaging-skia-0.0.1.crate", + "sha256": "a408014f1051ab534d388ab268acf700e73317796eab8aceecb7271203ad6b43", + "files": 9, + "unpacked_bytes": 57306 + }, + { + "name": "libremetaverse-openjpeg", + "version": "0.0.1", + "archive": "libremetaverse-openjpeg-0.0.1.crate", + "sha256": "72cee822e670b8db4d43c0ba03779e294b8b5c76aadfc50aa3c03d0295a3f18c", + "files": 8, + "unpacked_bytes": 39281 + }, + { + "name": "libremetaverse-opus", + "version": "0.0.1", + "archive": "libremetaverse-opus-0.0.1.crate", + "sha256": "a3d6a8a5796198d2773c7e5c48c186f98387eaf994f6d492af6dd29d8e4d486c", + "files": 6, + "unpacked_bytes": 12860 + }, + { + "name": "libremetaverse-prim-mesher", + "version": "0.0.1", + "archive": "libremetaverse-prim-mesher-0.0.1.crate", + "sha256": "436d85822576f512ba1223f0760786fc3050d02cb955d2191208caaa46706764", + "files": 12, + "unpacked_bytes": 208514 + }, + { + "name": "libremetaverse-lsl-tools", + "version": "0.0.1", + "archive": "libremetaverse-lsl-tools-0.0.1.crate", + "sha256": "3f8a9697939c837c1fe8abbcea72fc4449195aa56d409d01769ab9325009f0ee", + "files": 14, + "unpacked_bytes": 648214 + }, + { + "name": "libremetaverse", + "version": "0.0.1", + "archive": "libremetaverse-0.0.1.crate", + "sha256": "f06add7851122cfaece3589485ee027daf7f59e5ad7043207272a7e1085a8302", + "files": 90, + "unpacked_bytes": 28920093 + }, + { + "name": "libremetaverse-rendering-simple", + "version": "0.0.1", + "archive": "libremetaverse-rendering-simple-0.0.1.crate", + "sha256": "92bfb18f10275bb9daafd7f7372a7c7a2df03f150e6b5fa0444881db7ed76379", + "files": 9, + "unpacked_bytes": 92992 + }, + { + "name": "libremetaverse-rendering-mesh-foundry", + "version": "0.0.1", + "archive": "libremetaverse-rendering-mesh-foundry-0.0.1.crate", + "sha256": "6b17528c8c67270c0d84a3bad2c4fb580f123b6db38f296dabcdd97aa798fc16", + "files": 10, + "unpacked_bytes": 145049 + }, + { + "name": "libremetaverse-rlv", + "version": "0.0.1", + "archive": "libremetaverse-rlv-0.0.1.crate", + "sha256": "c32d9b5eea214abe415f1463a20b985a426fa01fcdaf49a6511d5be3b110eef8", + "files": 12, + "unpacked_bytes": 487158 + }, + { + "name": "libremetaverse-utilities", + "version": "0.0.1", + "archive": "libremetaverse-utilities-0.0.1.crate", + "sha256": "2f339ac2503cf76e352c97f5fb85be62965db4510766c931e17b686bed7f19f6", + "files": 6, + "unpacked_bytes": 67245 + }, + { + "name": "libremetaverse-voice-vivox", + "version": "0.0.1", + "archive": "libremetaverse-voice-vivox-0.0.1.crate", + "sha256": "914ccd0f2c5007828593b3edc7ee76289d37b50b422fd83254604f443b4fe651", + "files": 7, + "unpacked_bytes": 431677 + }, + { + "name": "libremetaverse-voice-webrtc", + "version": "0.0.1", + "archive": "libremetaverse-voice-webrtc-0.0.1.crate", + "sha256": "e525c71c95d6e4ff4b98174ca13e5e175e9eb9e8a8cc9df693137c1d2165466c", + "files": 7, + "unpacked_bytes": 317685 + }, + { + "name": "libremetaverse-programs", + "version": "0.0.1", + "archive": "libremetaverse-programs-0.0.1.crate", + "sha256": "f3c4173c601f0e74f160055de9fb751d375525e00a2d9b398e91e96eacb74247", + "files": 47, + "unpacked_bytes": 1032987 + } + ], + "binaries": [ + { + "name": "inventory-explorer", + "format": "ELF", + "bytes": 22508072, + "sha256": "bcbd78deb7b740db6b7fb396581a4316e47d7d81cc9ed9f9ccc13acbc156f82b", + "linked_libraries": [ + "ld-linux-x86-64.so.2", + "libc.so.6", + "libgcc_s.so.1", + "libm.so.6", + "libstdc++.so.6" + ], + "imported_symbols": 189, + "services": [ + "OpenSim login, capabilities, AIS, UDP when explicitly given credentials" + ], + "smokes": [ + { + "arguments": [ + "--help" + ], + "exit_code": 0, + "output_created": false + } + ] + }, + { + "name": "irc-gateway", + "format": "ELF", + "bytes": 22257728, + "sha256": "56c6bbf4529567871ec2f3b04354d565b77b7630a06ce66f52b0955e5dd41a9e", + "linked_libraries": [ + "ld-linux-x86-64.so.2", + "libc.so.6", + "libgcc_s.so.1", + "libm.so.6", + "libstdc++.so.6" + ], + "imported_symbols": 189, + "services": [ + "OpenSim and IRC TCP when explicitly given credentials and an IRC endpoint" + ], + "smokes": [ + { + "arguments": [ + "--help" + ], + "exit_code": 0, + "output_created": false + } + ] + }, + { + "name": "live-grid-smoke", + "format": "ELF", + "bytes": 23545976, + "sha256": "6d0016b083b80c1d0a557d459ad278e5ef1e7d7d685185113f9115ed6db1db2d", + "linked_libraries": [ + "ld-linux-x86-64.so.2", + "libc.so.6", + "libgcc_s.so.1", + "libm.so.6", + "libstdc++.so.6" + ], + "imported_symbols": 194, + "services": [ + "none in audit/fake mode; explicit OpenSim endpoint in live mode" + ], + "smokes": [ + { + "arguments": [ + "--help" + ], + "exit_code": 0, + "output_created": false + }, + { + "arguments": [ + "--audit-only" + ], + "exit_code": 0, + "output_created": false + }, + { + "arguments": [ + "--fake", + "--evidence", + "{output}" + ], + "exit_code": 0, + "output_created": true + } + ] + }, + { + "name": "osd-inspector", + "format": "ELF", + "bytes": 7132752, + "sha256": "314836cbcda99bec0dca737d3764c84350d61453ecf42c4cddab48924b6092f2", + "linked_libraries": [ + "ld-linux-x86-64.so.2", + "libc.so.6", + "libgcc_s.so.1", + "libstdc++.so.6" + ], + "imported_symbols": 94, + "services": [ + "none; offline files and standard streams only" + ], + "smokes": [ + { + "arguments": [ + "--help" + ], + "exit_code": 0, + "output_created": false + }, + { + "arguments": [ + "prim-to-osd" + ], + "exit_code": 0, + "output_created": false + } + ] + }, + { + "name": "packet-dump", + "format": "ELF", + "bytes": 22066600, + "sha256": "83dfc4ce10d86ed7b101adaee496c0850c8cfa7b37737056d66c4171a67732c3", + "linked_libraries": [ + "ld-linux-x86-64.so.2", + "libc.so.6", + "libgcc_s.so.1", + "libm.so.6", + "libstdc++.so.6" + ], + "imported_symbols": 189, + "services": [ + "OpenSim login and UDP when explicitly given credentials" + ], + "smokes": [ + { + "arguments": [ + "--help" + ], + "exit_code": 0, + "output_created": false + } + ] + }, + { + "name": "prim-inspector", + "format": "ELF", + "bytes": 22421512, + "sha256": "2ae167c2c66631411889c55ce3c16e80ffdf9a112591470905d1f27eec9ca597", + "linked_libraries": [ + "ld-linux-x86-64.so.2", + "libc.so.6", + "libgcc_s.so.1", + "libm.so.6", + "libstdc++.so.6" + ], + "imported_symbols": 188, + "services": [ + "OpenSim login, capabilities, and UDP when explicitly given credentials" + ], + "smokes": [ + { + "arguments": [ + "--help" + ], + "exit_code": 0, + "output_created": false + } + ] + }, + { + "name": "simple-bot", + "format": "ELF", + "bytes": 22110160, + "sha256": "dc9166108675090ed1bfd18bf0605bd0cb6eb36966d2797f59784aaada66b66c", + "linked_libraries": [ + "ld-linux-x86-64.so.2", + "libc.so.6", + "libgcc_s.so.1", + "libm.so.6", + "libstdc++.so.6" + ], + "imported_symbols": 189, + "services": [ + "OpenSim login, capabilities, and UDP when explicitly given credentials" + ], + "smokes": [ + { + "arguments": [ + "--help" + ], + "exit_code": 0, + "output_created": false + } + ] + }, + { + "name": "test-client", + "format": "ELF", + "bytes": 37352456, + "sha256": "3bb221c175ec5b0c16095b2cb533b68e24bf27ad53fcafb7e54477c60183ac3b", + "linked_libraries": [ + "ld-linux-x86-64.so.2", + "libc.so.6", + "libgcc_s.so.1", + "libm.so.6", + "libopenjp2.so.7", + "libstdc++.so.6" + ], + "imported_symbols": 299, + "services": [ + "OpenSim services selected by explicit login and commands" + ], + "smokes": [ + { + "arguments": [ + "--help" + ], + "exit_code": 0, + "output_created": false + } + ] + }, + { + "name": "vivox-test", + "format": "ELF", + "bytes": 22038904, + "sha256": "b3f91293f91c387360aa6becdb066dfef84b14b1a430d02c112a6799970696b0", + "linked_libraries": [ + "ld-linux-x86-64.so.2", + "libc.so.6", + "libgcc_s.so.1", + "libm.so.6", + "libstdc++.so.6" + ], + "imported_symbols": 191, + "services": [ + "none in help mode; explicit external Vivox control service in live mode" + ], + "smokes": [ + { + "arguments": [ + "--help" + ], + "exit_code": 0, + "output_created": false + } + ] + }, + { + "name": "webrtc-test", + "format": "ELF", + "bytes": 28176504, + "sha256": "f0db73115ad2c4cad7e187ae29ececde6f885fd9501398bd461fe2da755e594f", + "linked_libraries": [ + "ld-linux-x86-64.so.2", + "libc.so.6", + "libgcc_s.so.1", + "libm.so.6", + "libopus.so.0", + "libstdc++.so.6" + ], + "imported_symbols": 201, + "services": [ + "IPv4 loopback in fake mode; explicit OpenSim voice capabilities in live mode" + ], + "smokes": [ + { + "arguments": [ + "--help" + ], + "exit_code": 0, + "output_created": false + }, + { + "arguments": [ + "--fake", + "--timeout-seconds", + "10" + ], + "exit_code": 0, + "output_created": false + } + ] + } + ], + "clean_package_build": true, + "reference_checkout_required": false, + "helper_processes_invoked": [], + "forbidden_findings": 0 +} diff --git a/ci/provenance-policy.json b/ci/provenance-policy.json index aaf1634..fcafd74 100644 --- a/ci/provenance-policy.json +++ b/ci/provenance-policy.json @@ -28,6 +28,7 @@ {"path":"codegen/inputs/attentions.xml","sha256":"bdbc4ca02dd3bde78b86e796f7eb4ada0000e47be9a127e35d2b77f3010a20af","kind":"linden-data","origin":"LibreMetaverse/linden/character/attentions.xml","license":"CC-BY-SA-3.0","distribution":"source-and-generated-data"}, {"path":"codegen/inputs/attentionsN.xml","sha256":"80055e12a04d05ef5bb16e8c542cd6c218735f8c72f7e48317b8a34282d1ac5b","kind":"linden-data","origin":"LibreMetaverse/linden/character/attentionsN.xml","license":"CC-BY-SA-3.0","distribution":"source-and-generated-data"}, {"path":"codegen/inputs/avatar_lad.xml","sha256":"6f97a1408fa8f0ab5c09043da433c701701fe075e8a97867fcbcda207adf9eaf","kind":"linden-data","origin":"LibreMetaverse/linden/character/avatar_lad.xml","license":"CC-BY-SA-3.0","distribution":"source-and-generated-data"}, + {"path":"crates/libremetaverse/assets/avatar_lad.xml","sha256":"6f97a1408fa8f0ab5c09043da433c701701fe075e8a97867fcbcda207adf9eaf","kind":"linden-data-package-mirror","origin":"codegen/inputs/avatar_lad.xml","license":"CC-BY-SA-3.0","distribution":"source-package-runtime-data"}, {"path":"codegen/inputs/avatar_skeleton.xml","sha256":"addb3cc4cd582ed4343d778e06daf90c6b77b61b4ecb4a20d4bf202c2b93908a","kind":"linden-data","origin":"LibreMetaverse/linden/character/avatar_skeleton.xml","license":"CC-BY-SA-3.0","distribution":"source-and-generated-data"}, {"path":"codegen/inputs/genepool.xml","sha256":"6fa1391cd98fddec0d7fe762343ea21e53a655a8ddd4f33af52f8a02aaa3f56d","kind":"linden-data","origin":"LibreMetaverse/linden/character/genepool.xml","license":"CC-BY-SA-3.0","distribution":"source-and-generated-data"}, {"path":"codegen/inputs/grass.xml","sha256":"90015f9768afe3cfee761d04239bf9e284b2739b095b7ab8331966fa1fbfcb4e","kind":"linden-data","origin":"LibreMetaverse/linden/grass.xml","license":"CC-BY-SA-3.0","distribution":"source-and-generated-data"}, diff --git a/crates/libremetaverse-imaging/Cargo.toml b/crates/libremetaverse-imaging/Cargo.toml index 8a8f050..c2e8047 100644 --- a/crates/libremetaverse-imaging/Cargo.toml +++ b/crates/libremetaverse-imaging/Cargo.toml @@ -12,8 +12,8 @@ default = [] jpeg2000 = ["dep:libremetaverse-openjpeg"] [dependencies] -libremetaverse-types = { path = "../libremetaverse-types" } -libremetaverse-openjpeg = { path = "../libremetaverse-openjpeg", optional = true } +libremetaverse-types = { version = "0.0.1", path = "../libremetaverse-types" } +libremetaverse-openjpeg = { version = "0.0.1", path = "../libremetaverse-openjpeg", optional = true } [dev-dependencies] stats_alloc = "0.1.10" diff --git a/crates/libremetaverse-lsl-tools/Cargo.toml b/crates/libremetaverse-lsl-tools/Cargo.toml index 72c3a9a..87fe116 100644 --- a/crates/libremetaverse-lsl-tools/Cargo.toml +++ b/crates/libremetaverse-lsl-tools/Cargo.toml @@ -8,7 +8,7 @@ repository.workspace = true description = "Native LSL lexer and parser tooling for the MetaCrate LibreMetaverse rewrite" [dependencies] -libremetaverse-types = { path = "../libremetaverse-types" } +libremetaverse-types = { version = "0.0.1", path = "../libremetaverse-types" } unicode-general-category = "1.1" regex = "1" diff --git a/crates/libremetaverse-openjpeg/Cargo.toml b/crates/libremetaverse-openjpeg/Cargo.toml index b4679b6..4113ccc 100644 --- a/crates/libremetaverse-openjpeg/Cargo.toml +++ b/crates/libremetaverse-openjpeg/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "libremetaverse-openjpeg" -version = "0.0.1" -edition = "2024" -rust-version = "1.96" -license = "BSD-3-Clause" -publish = false +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true description = "Safe, bounded OpenJPEG 2.5.4 adapter for MetaCrate" build = "build.rs" diff --git a/crates/libremetaverse-opus/Cargo.toml b/crates/libremetaverse-opus/Cargo.toml index 960223b..0127fb2 100644 --- a/crates/libremetaverse-opus/Cargo.toml +++ b/crates/libremetaverse-opus/Cargo.toml @@ -6,7 +6,6 @@ rust-version.workspace = true license.workspace = true repository.workspace = true description = "Safe system-libopus adapter for MetaCrate" -publish = false build = "build.rs" [build-dependencies] diff --git a/crates/libremetaverse-prim-mesher/Cargo.toml b/crates/libremetaverse-prim-mesher/Cargo.toml index d966b53..cbed1b7 100644 --- a/crates/libremetaverse-prim-mesher/Cargo.toml +++ b/crates/libremetaverse-prim-mesher/Cargo.toml @@ -8,8 +8,8 @@ repository.workspace = true description = "Primitive meshing shims for the MetaCrate LibreMetaverse rewrite" [dependencies] -libremetaverse-imaging = { path = "../libremetaverse-imaging" } -libremetaverse-types = { path = "../libremetaverse-types" } +libremetaverse-imaging = { version = "0.0.1", path = "../libremetaverse-imaging" } +libremetaverse-types = { version = "0.0.1", path = "../libremetaverse-types" } [dev-dependencies] stats_alloc = "0.1.10" diff --git a/crates/libremetaverse-rendering-mesh-foundry/Cargo.toml b/crates/libremetaverse-rendering-mesh-foundry/Cargo.toml index c34727c..06b401d 100644 --- a/crates/libremetaverse-rendering-mesh-foundry/Cargo.toml +++ b/crates/libremetaverse-rendering-mesh-foundry/Cargo.toml @@ -9,12 +9,12 @@ description = "Bounded native MeshFoundry renderer for the MetaCrate LibreMetave [dependencies] flate2 = "1.1.2" -libremetaverse = { path = "../libremetaverse" } -libremetaverse-imaging = { path = "../libremetaverse-imaging" } -libremetaverse-prim-mesher = { path = "../libremetaverse-prim-mesher" } -libremetaverse-rendering-simple = { path = "../libremetaverse-rendering-simple" } -libremetaverse-structured-data = { path = "../libremetaverse-structured-data" } -libremetaverse-types = { path = "../libremetaverse-types" } +libremetaverse = { version = "0.0.1", path = "../libremetaverse" } +libremetaverse-imaging = { version = "0.0.1", path = "../libremetaverse-imaging" } +libremetaverse-prim-mesher = { version = "0.0.1", path = "../libremetaverse-prim-mesher" } +libremetaverse-rendering-simple = { version = "0.0.1", path = "../libremetaverse-rendering-simple" } +libremetaverse-structured-data = { version = "0.0.1", path = "../libremetaverse-structured-data" } +libremetaverse-types = { version = "0.0.1", path = "../libremetaverse-types" } [dev-dependencies] stats_alloc = "0.1.10" diff --git a/crates/libremetaverse-rendering-simple/Cargo.toml b/crates/libremetaverse-rendering-simple/Cargo.toml index a6ce47c..67f8bfb 100644 --- a/crates/libremetaverse-rendering-simple/Cargo.toml +++ b/crates/libremetaverse-rendering-simple/Cargo.toml @@ -8,10 +8,10 @@ repository.workspace = true description = "Simple renderer shims for the MetaCrate LibreMetaverse rewrite" [dependencies] -libremetaverse = { path = "../libremetaverse" } -libremetaverse-imaging = { path = "../libremetaverse-imaging" } -libremetaverse-prim-mesher = { path = "../libremetaverse-prim-mesher" } -libremetaverse-types = { path = "../libremetaverse-types" } +libremetaverse = { version = "0.0.1", path = "../libremetaverse" } +libremetaverse-imaging = { version = "0.0.1", path = "../libremetaverse-imaging" } +libremetaverse-prim-mesher = { version = "0.0.1", path = "../libremetaverse-prim-mesher" } +libremetaverse-types = { version = "0.0.1", path = "../libremetaverse-types" } [lints] workspace = true diff --git a/crates/libremetaverse-rlv/Cargo.toml b/crates/libremetaverse-rlv/Cargo.toml index 9d6f093..fc66533 100644 --- a/crates/libremetaverse-rlv/Cargo.toml +++ b/crates/libremetaverse-rlv/Cargo.toml @@ -8,7 +8,7 @@ repository.workspace = true description = "Bounded native RLV protocol, state, inventory, lock, camera, and permission layer" [dependencies] -libremetaverse-types = { path = "../libremetaverse-types" } +libremetaverse-types = { version = "0.0.1", path = "../libremetaverse-types" } [lints] workspace = true diff --git a/crates/libremetaverse-structured-data/Cargo.toml b/crates/libremetaverse-structured-data/Cargo.toml index 6463a17..10afc4b 100644 --- a/crates/libremetaverse-structured-data/Cargo.toml +++ b/crates/libremetaverse-structured-data/Cargo.toml @@ -9,7 +9,7 @@ description = "OSD and LLSD shims for the MetaCrate LibreMetaverse rewrite" [dependencies] base64 = "0.22" -libremetaverse-types = { path = "../libremetaverse-types" } +libremetaverse-types = { version = "0.0.1", path = "../libremetaverse-types" } serde = "1" serde_json = "1" diff --git a/crates/libremetaverse-utilities/Cargo.toml b/crates/libremetaverse-utilities/Cargo.toml index a3fadf1..74c3ae3 100644 --- a/crates/libremetaverse-utilities/Cargo.toml +++ b/crates/libremetaverse-utilities/Cargo.toml @@ -8,9 +8,9 @@ repository.workspace = true description = "Utility shims for the MetaCrate LibreMetaverse rewrite" [dependencies] -libremetaverse = { path = "../libremetaverse" } -libremetaverse-structured-data = { path = "../libremetaverse-structured-data" } -libremetaverse-types = { path = "../libremetaverse-types" } +libremetaverse = { version = "0.0.1", path = "../libremetaverse" } +libremetaverse-structured-data = { version = "0.0.1", path = "../libremetaverse-structured-data" } +libremetaverse-types = { version = "0.0.1", path = "../libremetaverse-types" } [lints] workspace = true diff --git a/crates/libremetaverse-voice-vivox/Cargo.toml b/crates/libremetaverse-voice-vivox/Cargo.toml index 9e41b5b..aeb6d84 100644 --- a/crates/libremetaverse-voice-vivox/Cargo.toml +++ b/crates/libremetaverse-voice-vivox/Cargo.toml @@ -8,9 +8,9 @@ repository.workspace = true description = "Vivox voice shims for the MetaCrate LibreMetaverse rewrite" [dependencies] -libremetaverse = { path = "../libremetaverse" } -libremetaverse-structured-data = { path = "../libremetaverse-structured-data" } -libremetaverse-types = { path = "../libremetaverse-types" } +libremetaverse = { version = "0.0.1", path = "../libremetaverse" } +libremetaverse-structured-data = { version = "0.0.1", path = "../libremetaverse-structured-data" } +libremetaverse-types = { version = "0.0.1", path = "../libremetaverse-types" } roxmltree = "0.21.1" tokio = { version = "1.47.1", features = ["io-util", "net", "time"] } diff --git a/crates/libremetaverse-voice-webrtc/Cargo.toml b/crates/libremetaverse-voice-webrtc/Cargo.toml index ad82089..872d56f 100644 --- a/crates/libremetaverse-voice-webrtc/Cargo.toml +++ b/crates/libremetaverse-voice-webrtc/Cargo.toml @@ -9,10 +9,10 @@ description = "WebRTC voice shims for the MetaCrate LibreMetaverse rewrite" [dependencies] hound = "3.5" -libremetaverse = { path = "../libremetaverse" } -libremetaverse-opus = { path = "../libremetaverse-opus" } -libremetaverse-structured-data = { path = "../libremetaverse-structured-data" } -libremetaverse-types = { path = "../libremetaverse-types" } +libremetaverse = { version = "0.0.1", path = "../libremetaverse" } +libremetaverse-opus = { version = "0.0.1", path = "../libremetaverse-opus" } +libremetaverse-structured-data = { version = "0.0.1", path = "../libremetaverse-structured-data" } +libremetaverse-types = { version = "0.0.1", path = "../libremetaverse-types" } serde = { version = "1", features = ["derive"] } serde_json = "1" str0m = { version = "0.22", default-features = false, features = ["rust-crypto"] } diff --git a/crates/libremetaverse/Cargo.toml b/crates/libremetaverse/Cargo.toml index 455ce5c..e2dac41 100644 --- a/crates/libremetaverse/Cargo.toml +++ b/crates/libremetaverse/Cargo.toml @@ -19,9 +19,9 @@ bcdec_rs = { version = "0.2.0", optional = true } flate2 = "1.1.2" futures-channel = "0.3.31" futures-util = "0.3.31" -libremetaverse-imaging = { path = "../libremetaverse-imaging" } -libremetaverse-structured-data = { path = "../libremetaverse-structured-data" } -libremetaverse-types = { path = "../libremetaverse-types" } +libremetaverse-imaging = { version = "0.0.1", path = "../libremetaverse-imaging" } +libremetaverse-structured-data = { version = "0.0.1", path = "../libremetaverse-structured-data" } +libremetaverse-types = { version = "0.0.1", path = "../libremetaverse-types" } mac_address2 = "2.0.2" os_info = { version = "3.15.0", default-features = false } reqwest = { version = "0.13.4", default-features = false, features = ["rustls", "stream"] } diff --git a/crates/libremetaverse/README.md b/crates/libremetaverse/README.md new file mode 100644 index 0000000..dd40756 --- /dev/null +++ b/crates/libremetaverse/README.md @@ -0,0 +1,267 @@ +# Rust API and C# migration guide + +`MetaCrate` is a native Rust implementation of the API and behavior cataloged +from `LibreMetaverse`. The compatibility reference is the +[pinned C# source](https://github.com/cinderblocks/libremetaverse/tree/2aa70bb68513b39795da5d13c88f31b86e85a3ba); +it is never loaded, invoked, or shipped. Exact type/member mappings live in +[`RUST-TYPES.tsv`](https://git.rfc1437.de/hugo/MetaCrate/src/branch/main/api/RUST-TYPES.tsv) and +[`RUST-MAPPING.tsv`](https://git.rfc1437.de/hugo/MetaCrate/src/branch/main/api/RUST-MAPPING.tsv). Search those files by C# +documentation ID when a familiar member has a non-obvious Rust name. + +## Choosing crates and features + +Most applications start with `libremetaverse` and add narrower crates only when +they use those APIs directly. + +| Crate | Choose it for | Features or system boundary | +| --- | --- | --- | +| `libremetaverse` | Grid client, login, network, agents, inventory, assets, appearance, world, and social APIs | Default pure-Rust BC6H/BC7; optional `jpeg2000` and `vorbis` | +| `libremetaverse-types` | UUIDs, vectors, matrices, colors, cancellation, compatibility collections, and boundary types | Pure Rust | +| `libremetaverse-structured-data` | LLSD/OSD XML, JSON, binary, and notation | Pure Rust | +| `libremetaverse-imaging` | Managed images, TGA/DDS, codec traits | Optional `jpeg2000` | +| `libremetaverse-imaging-skia` | Common raster formats through Skia | Optional `skia`; native Skia build/cache | +| `libremetaverse-prim-mesher` | Legacy prim and sculpt geometry | Pure Rust | +| `libremetaverse-rendering-simple` | Deterministic reference geometry | Pure Rust | +| `libremetaverse-rendering-mesh-foundry` | Prim, terrain, sculpt, and mesh-asset rendering | Pure Rust | +| `libremetaverse-lsl-tools` | LSL lexing, parsing, diagnostics, and generation | Pure Rust | +| `libremetaverse-rlv` | RLV commands, restrictions, locks, camera, and inventory policy | Pure Rust | +| `libremetaverse-utilities` | Compatible utility helpers | Pure Rust | +| `libremetaverse-voice-vivox` | Vivox XML control protocol | External Vivox service is explicit and never spawned | +| `libremetaverse-voice-webrtc` | Native ICE/DTLS/SRTP/SCTP and Opus voice | System libopus; optional `real-audio` uses CPAL | +| `libremetaverse-openjpeg` | Audited `OpenJPEG` adapter | System `OpenJPEG` 2.5.4 or newer | +| `libremetaverse-opus` | Audited Opus encoder/decoder adapter | System libopus | + +Features are additive. Keep defaults unless you need a codec, and enable one +native adapter at a time while diagnosing installation problems. The +[release CI matrix](https://git.rfc1437.de/hugo/MetaCrate/src/branch/main/docs/release-ci-matrix.md) records every validated combination. + +## Naming and overload migration + +C# `PascalCase` types remain recognizable while methods and properties use +Rust `snake_case`. A property getter becomes `name()` and its setter becomes +`set_name(value)`. Events become `subscribe_*` methods returning an owned +subscription. Rust has no overloads, so the simplest form keeps the base name +and additional forms receive parameter-derived suffixes. The mapping ledger is +authoritative; do not guess a long suffix. + +```rust +use libremetaverse::InventoryItem; +use libremetaverse_types::UUID; + +let id = UUID::new_with_u_int64(42)?; +let mut item = InventoryItem::new_with_uuid(id)?; +item.base.set_name("Migrated item".into()); +assert_eq!(item.base.uuid(), id); +assert_eq!(item.base.name(), "Migrated item"); +# Ok::<(), libremetaverse::Error>(()) +``` + +C# `null` usually maps to `Option`, `ref`/`out` may become a return value or +an explicit mutable reference, and interface objects become `dyn Trait` behind +`Arc` or `Box` according to the ledger's `ownership` column. + +## Ownership and disposal + +Managers cloned from a `GridClient` share native state. `GridClient` owns its +cancellation root and cached services; event guards own registrations; session +objects own tasks and sockets. Explicit shutdown is recommended because it can +report errors, while `Drop` remains the final idempotent safety net. + +```rust +use libremetaverse::{ClientLifecycleState, GridClient}; + +let client = GridClient::new()?; +assert_eq!(client.lifecycle_state(), ClientLifecycleState::Active); +client.dispose_with_method()?; +client.dispose_with_method()?; // idempotent +assert_eq!(client.lifecycle_state(), ClientLifecycleState::Disposed); +# Ok::<(), libremetaverse::Error>(()) +``` + +The runnable version is +[`offline_client.rs`](https://git.rfc1437.de/hugo/MetaCrate/src/branch/main/crates/libremetaverse/examples/offline_client.rs). +Never hold a manager lock while calling user code or awaiting I/O. Services +registered through `GridClient::builder()` must join every owned task before +their `shutdown` method returns. + +## Async work and cancellation + +Async APIs borrow no hidden runtime. Call them from the application executor and +pass `CancellationToken` explicitly where the C# API accepted one. Cloning a +token is cheap and observes the same cancellation source. Cancellation is a +typed `Error::Cancelled`, not a successful empty response. + +```rust +use libremetaverse_types::compat::CancellationTokenSource; + +# #[tokio::main(flavor = "current_thread")] +# async fn main() -> Result<(), libremetaverse::Error> { +let source = CancellationTokenSource::new(); +let token = source.token(); +source.cancel(); +token.cancelled().await; +assert!(matches!( + token.throw_if_cancellation_requested(), + Err(libremetaverse::Error::Cancelled) +)); +# Ok(()) +# } +``` + +The same code is available as +[`cancellation.rs`](https://git.rfc1437.de/hugo/MetaCrate/src/branch/main/crates/libremetaverse/examples/cancellation.rs). +Timeouts belong at the caller or documented operation boundary; cancellation +must still drain and join the underlying resource owner. + +## Errors + +Fallible compatibility APIs return the shared `libremetaverse::Error`; native +composition APIs may expose a narrower error such as `ClientCoreError` or +`WebRtcError`. Match variants for control flow and use display text only for +operators. Errors and `Debug` output redact credentials, capability URLs, and +session secrets. + +```rust +use libremetaverse::http::DownloadRequest; +use libremetaverse_types::compat::Uri; + +let result = DownloadRequest::new(Uri("file:///not-http".into()), None, None); +assert!(matches!(result, Err(libremetaverse::Error::Argument))); +``` + +Do not translate C# exception swallowing into `unwrap_or_default()`. Preserve +the mapped error contract and handle cancellation separately from protocol, +I/O, authentication, and validation failures. + +## Events and subscriptions + +An `EventHandler` is an `Arc` callback. Keep the returned `Subscription` for +exactly as long as notifications are wanted; dropping it unregisters the +callback. Dispatch clones the handler list and releases internal locks before +calling user code. Manager event dispatch isolates subscriber panics so one +consumer cannot stop later consumers. + +```rust +use libremetaverse::{GridClient, Inventory, InventoryFolder}; +use libremetaverse_types::UUID; +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; + +let client = Arc::new(GridClient::new()?); +let inventory = Inventory::new_with_grid_client_uuid(Arc::clone(&client), UUID::zero())?; +let calls = Arc::new(AtomicUsize::new(0)); +let observed = Arc::clone(&calls); +let subscription = inventory.subscribe_inventory_object_added(Arc::new(move |_| { + observed.fetch_add(1, Ordering::AcqRel); +})); +let folder = InventoryFolder::new(UUID::new_with_u_int64(7)?)?; +inventory.update_node_for(&folder)?; +assert_eq!(calls.load(Ordering::Acquire), 1); +drop(subscription); +client.dispose_with_method()?; +# Ok::<(), libremetaverse::Error>(()) +``` + +## Threading and callbacks + +Public shared managers use `Arc`, atomics, mutexes, channels, and cancellation +tokens rather than a CLR synchronization context. A type being cloneable does +not make a callback re-entrant: keep handlers short, move expensive work to a +bounded queue, and never block an async runtime thread waiting for itself. +Download, inventory, client, and voice diagnostics expose owned task/queue +counts for shutdown checks. The [concurrency audit](https://git.rfc1437.de/hugo/MetaCrate/src/branch/main/docs/concurrency-hardening.md) +defines the exact baseline and soak thresholds. + +## Security boundaries + +The released graph contains no CLR host, .NET assembly loader, subprocess RPC, +or foreign `LibreMetaverse` bridge. HTTP uses rustls. Untrusted LLSD, archive, +image, packet, event, and signaling inputs have explicit size/depth limits. +Secrets belong in operation arguments or environment variables, never in +diagnostics, evidence, filenames, command lines, or committed fixtures. + +Offline/fake modes are real deterministic executions, not skipped live tests. +Live login and every mutating smoke action require their own opt-in and literal +confirmation. See the [live-grid boundary](https://git.rfc1437.de/hugo/MetaCrate/src/branch/main/docs/live-grid-smoke.md). + +## Native prerequisites + +The default client build needs no image-codec system library. Optional native +features require: + +- `OpenJPEG` 2.5.4 or newer for `jpeg2000`; +- the rust-skia prerequisites/cache for `skia`; +- system libopus for WebRTC voice and the `libremetaverse-opus` adapter; +- ALSA development headers on Linux, `CoreAudio` on macOS, or WASAPI on Windows + when `real-audio` enables CPAL; +- the Vorbis encoder build prerequisites for `vorbis`. + +Discovery uses `pkg-config` on Unix/macOS and `vcpkg` on Windows MSVC where +applicable. The adapters remain cross-platform; Gitea workflows intentionally +run only on `ubuntu-latest`. + +## Live `OpenSim` setup + +Use a dedicated `OpenSim` account and put credentials in the workspace `.env` or +the process environment. The login URL is used directly; no Second Life host is +substituted. + +```text +GRID_USER=First Last +GRID_PASSWORD=... +GRID_LOGIN_URL=https://your-opensim.example/login +``` + +Start with the credential-safe audit and fake smoke, then opt into live login: + +```sh +cargo run -p libremetaverse-programs --bin live-grid-smoke -- --audit-only +cargo run -p libremetaverse-programs --bin live-grid-smoke -- --fake +cargo run -p libremetaverse-programs --bin live-grid-smoke -- \ + --allow-live-login --confirm-live-login LOGIN +``` + +Chat, movement, and reversible inventory each require additional confirmations +documented in the [`OpenSim` live-grid guide](https://git.rfc1437.de/hugo/MetaCrate/src/branch/main/docs/live-grid-smoke.md). A test that +needs live credentials must fail clearly when they are absent; it must not +silently skip. + +## Programs and operational tools + +All pinned upstream programs have native Rust targets and deterministic offline +tests. Their arguments, exit statuses, fake/live boundaries, and focused test +commands are linked from the [program operations manual](https://git.rfc1437.de/hugo/MetaCrate/src/branch/main/programs/README.md): + +- [`osd-inspector`](https://git.rfc1437.de/hugo/MetaCrate/src/branch/main/programs/README.md#osdinspector) +- [`simple-bot`](https://git.rfc1437.de/hugo/MetaCrate/src/branch/main/programs/README.md#simplebot) +- [`packet-dump`](https://git.rfc1437.de/hugo/MetaCrate/src/branch/main/programs/README.md#packetdump) +- [`prim-inspector`](https://git.rfc1437.de/hugo/MetaCrate/src/branch/main/programs/README.md#priminspector) +- [`inventory-explorer`](https://git.rfc1437.de/hugo/MetaCrate/src/branch/main/programs/README.md#inventoryexplorer) +- [`irc-gateway`](https://git.rfc1437.de/hugo/MetaCrate/src/branch/main/programs/README.md#ircgateway) +- [`test-client`](https://git.rfc1437.de/hugo/MetaCrate/src/branch/main/programs/README.md#testclient) +- [`vivox-test`](https://git.rfc1437.de/hugo/MetaCrate/src/branch/main/programs/README.md#vivoxtest) +- [`webrtc-test`](https://git.rfc1437.de/hugo/MetaCrate/src/branch/main/programs/README.md#webrtctest) + +For services, run the client under an external supervisor, propagate shutdown +cancellation, bound queues/files, collect sanitized evidence, and call logout +before disposal. The tool never invents persistence or retry policy on behalf of +the application. + +## Documentation validation + +The complete documentation gate is: + +```sh +cargo run --locked -p metacrate-ci-matrix -- documentation-audit \ + --evidence /tmp/metacrate-documentation.json +RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --locked -j 1 +cargo test --locked -p libremetaverse --doc -j 1 +cargo test --locked -p libremetaverse --examples -j 1 +cargo run --locked -p libremetaverse --example offline_client +cargo run --locked -p libremetaverse --example cancellation +``` + +The generated [coverage report](https://git.rfc1437.de/hugo/MetaCrate/src/branch/main/api/DOCUMENTATION-COVERAGE.md) proves that +every mapped member/type retains its exact C# concept ID and mapping context, +all public crates have root docs, all local Markdown links resolve, all programs +are linked, and every Rust fence in this guide is compiled as a doctest. diff --git a/crates/libremetaverse/assets/avatar_lad.xml b/crates/libremetaverse/assets/avatar_lad.xml new file mode 100644 index 0000000..63e3265 --- /dev/null +++ b/crates/libremetaverse/assets/avatar_lad.xml @@ -0,0 +1,17504 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crates/libremetaverse/src/avatar_rig.rs b/crates/libremetaverse/src/avatar_rig.rs index c5f7e39..a270b68 100644 --- a/crates/libremetaverse/src/avatar_rig.rs +++ b/crates/libremetaverse/src/avatar_rig.rs @@ -113,7 +113,7 @@ impl LindenAvatarDefinition { owned_xml = read_bounded(&file_name)?; owned_xml.as_str() } else { - include_str!("../../../codegen/inputs/avatar_lad.xml") + include_str!("../assets/avatar_lad.xml") }; let document = Document::parse(xml).map_err(|_| Error::Argument)?; let mut attachment_points = Vec::new(); diff --git a/crates/libremetaverse/src/lib.rs b/crates/libremetaverse/src/lib.rs index bac899c..7ce8b5a 100644 --- a/crates/libremetaverse/src/lib.rs +++ b/crates/libremetaverse/src/lib.rs @@ -4,7 +4,7 @@ //! world, and social APIs. The appended guide covers crate selection, C# name //! migration, ownership, cancellation, events, security, and `OpenSim` setup. -#![doc = include_str!("../../../docs/rust-api-guide.md")] +#![doc = include_str!("../README.md")] extern crate self as libremetaverse; diff --git a/docs/release-artifacts.md b/docs/release-artifacts.md new file mode 100644 index 0000000..fb5b442 --- /dev/null +++ b/docs/release-artifacts.md @@ -0,0 +1,90 @@ +# Native release artifact audit + +MetaCrate's release gate builds and inspects every release crate and every +shipped program. The gate proves that the deliverables are native Rust +artifacts: they neither contain nor load LibreMetaverse assemblies, a CLR or +Mono host, generated foreign bindings, RPC bridges, downloaded fallbacks, or +helper subprocesses. + +The authoritative inventory and allowlists are +[`ci/artifact-policy.json`](../ci/artifact-policy.json). Adding a publishable +crate, binary target, native FFI boundary, copied package input, or offline +smoke requires an explicit policy update. The auditor rejects an incomplete or +duplicate inventory. + +## Clean Ubuntu prerequisites + +The automated gate runs only on `ubuntu-latest`, using Rust 1.97.1 for normal +release work. Rust 1.96.0 remains a separate minimum-supported-version check; +it is not used to build these release artifacts. The clean worker installs: + +- a C/C++ build toolchain, Clang, CMake, Ninja, `pkg-config`, Python 3, and + `curl` for the pinned Skia build/download path; +- Fontconfig and FreeType development libraries for Skia; +- OpenJPEG 2.5.4 through `tools/install_openjpeg_2_5_4.sh`; and +- the distribution Opus development package. + +No reference LibreMetaverse checkout, .NET SDK/runtime, Mono installation, +credentials, `.env` file, or live grid is present. The release runtime smoke is +deliberately offline. Live OpenSim validation is a separate credentialed gate +documented in [the live-grid guide](live-grid-smoke.md). + +## What the gate verifies + +The `artifact-audit` command performs all of these checks and fails closed: + +1. Resolve the locked Cargo graph and require all 16 release/source packages to + use versioned internal dependencies. Reject known managed-host dependencies. +2. Scan production Rust and manifest sources for helper processes, runtime + loading, managed assemblies, and unreviewed `extern "C"` boundaries. The only + native FFI allowlist is the reviewed OpenJPEG and Opus adapters. +3. Inspect every `.crate` archive with byte and entry limits. Reject path + traversal, links, special files, managed-code extensions, forbidden runtime + markers, and retained workspace dependency paths. +4. Extract the packages into a new temporary workspace and build every library + and binary offline. All internal crates are resolved from the extracted + archives and `METACRATE_REFERENCE_ROOT` points to a path that cannot exist. +5. Parse each executable as ELF, PE, or Mach-O and record imported symbols and + linked libraries. Reject forbidden CLR/Mono/LibreMetaverse markers. +6. Remove all grid credentials, install traps for common managed/download + helpers, disable external networking with invalid proxy endpoints, run + `--help` for every binary, and execute the declared deeper fake/offline + smokes. A helper invocation, timeout, external-service dependency, or absent + required output fails the audit. + +The resulting JSON records the source commit, exact Rust compiler, host, +package and executable hashes, binary formats, linked libraries, import counts, +runtime smoke results, and zero forbidden findings. It is created with +create-new semantics so prior evidence cannot be silently overwritten. + +## Reproducing the gate + +After installing the prerequisites above, package all release members in one +Cargo invocation. Atomic packaging lets Cargo resolve the versioned internal +crate set exactly as a registry publication batch would: + +```sh +cargo package --locked --no-verify \ + -p libremetaverse-types -p libremetaverse-structured-data \ + -p libremetaverse-imaging -p libremetaverse-imaging-skia \ + -p libremetaverse-openjpeg -p libremetaverse-opus \ + -p libremetaverse-prim-mesher -p libremetaverse-lsl-tools \ + -p libremetaverse -p libremetaverse-rendering-simple \ + -p libremetaverse-rendering-mesh-foundry -p libremetaverse-rlv \ + -p libremetaverse-utilities -p libremetaverse-voice-vivox \ + -p libremetaverse-voice-webrtc -p libremetaverse-programs + +METACRATE_RUST_COMMIT=$(git rev-parse --verify HEAD) \ + cargo build --locked --release -j 1 -p libremetaverse-programs --bins + +cargo run --locked -p metacrate-ci-matrix -- artifact-audit \ + --artifact-dir target/release \ + --package-dir target/package \ + --evidence artifacts/release/artifact-audit.json +``` + +`--no-verify` prevents Cargo from building each archive redundantly. It does +not skip release verification: the following audit extracts all archives and +performs one stricter offline build of the complete package set. The generated +core is large, so the workspace release profile uses bounded optimization and +single-job CI to remain within an 8 GiB clean worker. diff --git a/programs/Cargo.toml b/programs/Cargo.toml index d32b65e..913c774 100644 --- a/programs/Cargo.toml +++ b/programs/Cargo.toml @@ -4,16 +4,18 @@ version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true +repository.workspace = true +description = "Native diagnostic and example programs for the MetaCrate LibreMetaverse rewrite" publish = false [dependencies] clap = { version = "4.5", features = ["derive"] } -libremetaverse = { path = "../crates/libremetaverse", default-features = false } -libremetaverse-imaging = { path = "../crates/libremetaverse-imaging", features = ["jpeg2000"] } -libremetaverse-imaging-skia = { path = "../crates/libremetaverse-imaging-skia", features = ["skia"] } -libremetaverse-structured-data = { path = "../crates/libremetaverse-structured-data" } -libremetaverse-voice-vivox = { path = "../crates/libremetaverse-voice-vivox" } -libremetaverse-voice-webrtc = { path = "../crates/libremetaverse-voice-webrtc" } +libremetaverse = { version = "0.0.1", path = "../crates/libremetaverse", default-features = false } +libremetaverse-imaging = { version = "0.0.1", path = "../crates/libremetaverse-imaging", features = ["jpeg2000"] } +libremetaverse-imaging-skia = { version = "0.0.1", path = "../crates/libremetaverse-imaging-skia", features = ["skia"] } +libremetaverse-structured-data = { version = "0.0.1", path = "../crates/libremetaverse-structured-data" } +libremetaverse-voice-vivox = { version = "0.0.1", path = "../crates/libremetaverse-voice-vivox" } +libremetaverse-voice-webrtc = { version = "0.0.1", path = "../crates/libremetaverse-voice-webrtc" } regex = "1.12" roxmltree = "0.21.1" serde = { version = "1", features = ["derive"] } diff --git a/programs/build.rs b/programs/build.rs index c6f008c..2dd0822 100644 --- a/programs/build.rs +++ b/programs/build.rs @@ -1,46 +1,12 @@ -use std::process::Command; - fn main() { println!("cargo:rerun-if-env-changed=METACRATE_RUST_COMMIT"); - println!("cargo:rerun-if-changed=../.git/HEAD"); - println!("cargo:rerun-if-changed=../.git/packed-refs"); - if let Some(reference) = git_reference() { - println!("cargo:rerun-if-changed=../.git/{reference}"); - } - let commit = std::env::var("METACRATE_RUST_COMMIT") .ok() .filter(|value| valid_commit(value)) - .or_else(git_commit) .unwrap_or_else(|| "unavailable".to_owned()); println!("cargo:rustc-env=METACRATE_RUST_COMMIT={commit}"); } -fn git_reference() -> Option { - let output = Command::new("git") - .args(["symbolic-ref", "--quiet", "HEAD"]) - .output() - .ok()?; - let reference = String::from_utf8(output.stdout).ok()?; - let reference = reference.trim(); - (output.status.success() - && reference.starts_with("refs/") - && reference - .bytes() - .all(|byte| byte.is_ascii_alphanumeric() || b"/-_.".contains(&byte))) - .then(|| reference.to_owned()) -} - -fn git_commit() -> Option { - let output = Command::new("git") - .args(["rev-parse", "--verify", "HEAD"]) - .output() - .ok()?; - let commit = String::from_utf8(output.stdout).ok()?; - let commit = commit.trim(); - (output.status.success() && valid_commit(commit)).then(|| commit.to_owned()) -} - fn valid_commit(value: &str) -> bool { matches!(value.len(), 40 | 64) && value.bytes().all(|byte| byte.is_ascii_hexdigit()) } diff --git a/release/DEPENDENCY-LICENSES.json b/release/DEPENDENCY-LICENSES.json index a39b42c..84e7106 100644 --- a/release/DEPENDENCY-LICENSES.json +++ b/release/DEPENDENCY-LICENSES.json @@ -1,6 +1,6 @@ { "schema": 1, - "cargo_lock_sha256": "ff5b851139156d43915f08494c5723638d3b48abb9d7ab5de6eea9e9f5b5554b", + "cargo_lock_sha256": "b0f9593fd3cae923e08d9a75fb16772e749a0dfddafaadb7f2a610bc597284ac", "packages": [ { "name": "adler2", @@ -1806,6 +1806,19 @@ } ] }, + { + "name": "goblin", + "version": "0.10.5", + "checksum": "983a6aafb3b12d4c41ea78d39e189af4298ce747353945ff5105b54a056e5cd9", + "license": "MIT", + "repository": "https://github.com/m4b/goblin", + "notice_files": [ + { + "path": "LICENSE", + "sha256": "655e3ee7a4c27430774962e62a6d37d7348e5f2f292010ad674ce1bebefd24bc" + } + ] + }, { "name": "group", "version": "0.13.0", @@ -3376,6 +3389,23 @@ } ] }, + { + "name": "plain", + "version": "0.2.3", + "checksum": "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6", + "license": "MIT/Apache-2.0", + "repository": "https://github.com/randomites/plain", + "notice_files": [ + { + "path": "LICENSE-APACHE", + "sha256": "a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2" + }, + { + "path": "LICENSE-MIT", + "sha256": "bc12b75fd81829814d843a03fa52aad0e53355b1f13665e309ff7fa33c66e5b5" + } + ] + }, { "name": "poly1305", "version": "0.8.0", @@ -4151,6 +4181,32 @@ } ] }, + { + "name": "scroll", + "version": "0.13.0", + "checksum": "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add", + "license": "MIT", + "repository": "https://github.com/m4b/scroll", + "notice_files": [ + { + "path": "LICENSE", + "sha256": "6e24b7455f0b9afefdf4f3efd59a56ce76a3020c2dc4371937e281fc5e587fd7" + } + ] + }, + { + "name": "scroll_derive", + "version": "0.13.1", + "checksum": "ed76efe62313ab6610570951494bdaa81568026e0318eaa55f167de70eeea67d", + "license": "MIT", + "repository": "https://github.com/m4b/scroll", + "notice_files": [ + { + "path": "LICENSE", + "sha256": "b35d026cc7aca9d5859a02eb87ddf7a386a24c986838651bd1f283f94e003327" + } + ] + }, { "name": "sctp-proto", "version": "0.10.3", diff --git a/release/DISTRIBUTION-MANIFEST.json b/release/DISTRIBUTION-MANIFEST.json index de6b1c9..6e86ba5 100644 --- a/release/DISTRIBUTION-MANIFEST.json +++ b/release/DISTRIBUTION-MANIFEST.json @@ -1,7 +1,7 @@ { "schema": 1, - "cargo_lock_sha256": "ff5b851139156d43915f08494c5723638d3b48abb9d7ab5de6eea9e9f5b5554b", - "provenance_policy_sha256": "045859dcf317efc19e6e47fc73dfc15726f7a787556a3960c4732aa5f3afde7c", + "cargo_lock_sha256": "b0f9593fd3cae923e08d9a75fb16772e749a0dfddafaadb7f2a610bc597284ac", + "provenance_policy_sha256": "f95475a2284ab282e9432865d4a7ed103498a56ed5ec2393017bc312c77da890", "upstream_repository": "https://github.com/cinderblocks/libremetaverse", "upstream_commit": "2aa70bb68513b39795da5d13c88f31b86e85a3ba", "source_files": [ @@ -10,11 +10,21 @@ "bytes": 164, "sha256": "0dd74dc81b612f3522abf818c4a31db298498f83cd198cc50982a27d19721d40" }, + { + "path": ".gitattributes", + "bytes": 56, + "sha256": "a7c5358f9d0b0ed236519fec9d5d4faee9c04127eedac9ba3ab536d3dcf1b1f2" + }, { "path": ".gitea/workflows/api-surface.yml", "bytes": 2661, "sha256": "d9e1890ad948d11d45b5d70fa30c30071dfa9e4c72989b9caa1afa58a55793ce" }, + { + "path": ".gitea/workflows/artifact-audit.yml", + "bytes": 3460, + "sha256": "af35254faef2be9b678820e14a73fddaff323c66821cd8415d319351ec3e2130" + }, { "path": ".gitea/workflows/codegen.yml", "bytes": 7978, @@ -77,13 +87,13 @@ }, { "path": "Cargo.lock", - "bytes": 99427, - "sha256": "ff5b851139156d43915f08494c5723638d3b48abb9d7ab5de6eea9e9f5b5554b" + "bytes": 100372, + "sha256": "b0f9593fd3cae923e08d9a75fb16772e749a0dfddafaadb7f2a610bc597284ac" }, { "path": "Cargo.toml", - "bytes": 1480, - "sha256": "fa9a7e393968e0080672245dc7dc05b046c51307cdf734cc176f183e1d2e7404" + "bytes": 1766, + "sha256": "b1f014e37d153c46b56f971e99963a8a9a51cd9e30816331068f9d08bce02449" }, { "path": "LICENSE.md", @@ -97,13 +107,13 @@ }, { "path": "README.md", - "bytes": 34694, - "sha256": "34deadf940ab9e9f849e58d56f512504e0c816fd5df1c848ac15c2b8f1f638ce" + "bytes": 34926, + "sha256": "dd0508a28b65ea53961bf9df7a1ddc2b7906a96545962c523dc0fa204dad805f" }, { "path": "RUSTREWRITE.md", - "bytes": 53386, - "sha256": "23981fd78a1664d86393a9e0bdbf3833025917294fa622f40c9b3e0cc28d2063" + "bytes": 54108, + "sha256": "6da24d9e0c2ca89122b4daa494376097acd90820efa5668654f4742bf484f965" }, { "path": "api/API-COVERAGE.md", @@ -113,7 +123,7 @@ { "path": "api/DOCUMENTATION-COVERAGE.md", "bytes": 911, - "sha256": "a0552d4869c03f221f105070f8639a4c4963a1c6c46ac073cff82782d2a8ebb0" + "sha256": "0d05fc115a7754074ba9c084654556ac32ce07b006236d21d406223cc5074de9" }, { "path": "api/MAPPING-COVERAGE.md", @@ -235,6 +245,11 @@ "bytes": 37699, "sha256": "9ffc240ca35d397ee848a6b313efcfefcf186ea01f61911aa3329cb2dcefd202" }, + { + "path": "ci/artifact-policy.json", + "bytes": 3011, + "sha256": "718c065c78377c846ccd962922e06b19de276c32f427f73a026060eec9b7a45c" + }, { "path": "ci/concurrency-thresholds.json", "bytes": 169, @@ -242,14 +257,19 @@ }, { "path": "ci/dependency-policy.json", - "bytes": 10365, - "sha256": "4f545b31c6ea2d97eadc5a60423d13839cc9a71447a603806a8690de4c56f226" + "bytes": 10628, + "sha256": "2807ccae43c9f0cf011978e5d63217aa3ac827406b52db2b04945ce4064f988e" }, { "path": "ci/evidence/api-audit.json", "bytes": 540, "sha256": "4e68a873ade8dc0b389c27ba8d377079abd441bd8fa34605e799e36899f8b77a" }, + { + "path": "ci/evidence/artifact-audit.json", + "bytes": 11830, + "sha256": "9c9c6ed236d04880bab504387251f7a9e283f338c48fe46a6fe0ece472eb8063" + }, { "path": "ci/evidence/concurrency-audit.json", "bytes": 1596, @@ -262,8 +282,8 @@ }, { "path": "ci/provenance-policy.json", - "bytes": 11311, - "sha256": "045859dcf317efc19e6e47fc73dfc15726f7a787556a3960c4732aa5f3afde7c" + "bytes": 11594, + "sha256": "f95475a2284ab282e9432865d4a7ed103498a56ed5ec2393017bc312c77da890" }, { "path": "ci/release-matrix.json", @@ -362,8 +382,8 @@ }, { "path": "crates/libremetaverse-imaging/Cargo.toml", - "bytes": 606, - "sha256": "022966d69dc37d0b230ba6c4f909c7eb0e659c37d8a2f0ffd90eadd011d924d5" + "bytes": 644, + "sha256": "b28b70a09f4916624dfb1d54869a4084996615304b7e04449d331ccd067ff20b" }, { "path": "crates/libremetaverse-imaging/benches/image_pipeline.rs", @@ -397,8 +417,8 @@ }, { "path": "crates/libremetaverse-lsl-tools/Cargo.toml", - "bytes": 417, - "sha256": "068fbbc78e5d447713c05316ef933e069e9a398ed5432ef325936d6ab4448031" + "bytes": 436, + "sha256": "aa30ad4c2c1f6c1119255c9e34d85b62019945127059682e6e1ff88a9268fbd3" }, { "path": "crates/libremetaverse-lsl-tools/README.md", @@ -452,8 +472,8 @@ }, { "path": "crates/libremetaverse-openjpeg/Cargo.toml", - "bytes": 774, - "sha256": "5a51bb7dfc922a68a0f2e164d5f00139c8cfea6327cda62a1810ae7e3efaa0ff" + "bytes": 809, + "sha256": "34c688a067c3718ede2686014feaa7910de33c3977c720fdae4bdfba089e7b54" }, { "path": "crates/libremetaverse-openjpeg/README.md", @@ -477,8 +497,8 @@ }, { "path": "crates/libremetaverse-opus/Cargo.toml", - "bytes": 805, - "sha256": "c0cbeb64ae8938f47ccbadef799789c201e79ca6d5bd97020fba0119c83a350c" + "bytes": 789, + "sha256": "5a055b7d3f3b90bdcd5db3406683f393a730f3c701bbc441b94768a7e252938c" }, { "path": "crates/libremetaverse-opus/build.rs", @@ -492,8 +512,8 @@ }, { "path": "crates/libremetaverse-prim-mesher/Cargo.toml", - "bytes": 513, - "sha256": "d7ff49658b6914febeb1fe26e5208fdeeb5e2f99bc93470ae81e0e9deaf50952" + "bytes": 551, + "sha256": "b4d44c464d3927c6b16b8bd917d19e0580173376f3a7f1be65a7e9dd6b98a00e" }, { "path": "crates/libremetaverse-prim-mesher/benches/meshing.rs", @@ -537,8 +557,8 @@ }, { "path": "crates/libremetaverse-rendering-mesh-foundry/Cargo.toml", - "bytes": 839, - "sha256": "e1f729b55a3e1a05b4637cfb92c9e4842d4a4aa4b08f4f1d4dff0a03c0db0ec0" + "bytes": 953, + "sha256": "3b9f3cedf6f16b2c85068db821e1d65a790a2b157058536d0634edb0affc98a7" }, { "path": "crates/libremetaverse-rendering-mesh-foundry/README.md", @@ -572,8 +592,8 @@ }, { "path": "crates/libremetaverse-rendering-simple/Cargo.toml", - "bytes": 549, - "sha256": "d95b49485b7636cdcf845a0bbab0c0eec0de92412ef1ce44bf7902c24610b0a0" + "bytes": 625, + "sha256": "f3ced1ce8f17688a3315378ba3bcfc52683bbae71c6a346427d5246eb3223c58" }, { "path": "crates/libremetaverse-rendering-simple/README.md", @@ -602,8 +622,8 @@ }, { "path": "crates/libremetaverse-rlv/Cargo.toml", - "bytes": 371, - "sha256": "52d33afce3bb0b9d76e4a3724670da6fd019c33b8d920bea3db807a4dcc17f9c" + "bytes": 390, + "sha256": "d0b65cc3e05a38d7e1f41d3863ecbb05820b5a118b9be3cca7370757080965af" }, { "path": "crates/libremetaverse-rlv/README.md", @@ -647,8 +667,8 @@ }, { "path": "crates/libremetaverse-structured-data/Cargo.toml", - "bytes": 406, - "sha256": "997000fc995703889f7e56034ed0bafdb0041a60e1d984308167b8a1a688ce84" + "bytes": 425, + "sha256": "d86210a9d4a424c0feec5d1c3aed66892ed107364e4e47c751c5d1a4dd60f5f3" }, { "path": "crates/libremetaverse-structured-data/src/binary.rs", @@ -782,8 +802,8 @@ }, { "path": "crates/libremetaverse-utilities/Cargo.toml", - "bytes": 478, - "sha256": "c9ab8ad171508aba0cb094afb92aeb53acd19e6e0010e844bec73bdbad094009" + "bytes": 535, + "sha256": "44c88ae2dd2cfb8f1390f9fd671c92d96b7e9c792535bafde34d0f2c161c1c16" }, { "path": "crates/libremetaverse-utilities/src/generated.rs", @@ -797,8 +817,8 @@ }, { "path": "crates/libremetaverse-voice-vivox/Cargo.toml", - "bytes": 575, - "sha256": "c11ff56052be98f09dfc9857678f35e90712769aa48d428a99d50a7feab13ed3" + "bytes": 632, + "sha256": "b00fe50a1e970bed33f6d15bac32fcac8ed8594555fce83db69c3aa3a45316ba" }, { "path": "crates/libremetaverse-voice-vivox/src/generated.rs", @@ -817,8 +837,8 @@ }, { "path": "crates/libremetaverse-voice-webrtc/Cargo.toml", - "bytes": 895, - "sha256": "51cadef6bc1232cd9df0e5eddfcb33f688ea638b187de9b6055501408b06d57e" + "bytes": 971, + "sha256": "ac1a5ed7deb2e33ff29a29eeb12d42227a75ee3766b7242e07e9881f92726b1d" }, { "path": "crates/libremetaverse-voice-webrtc/src/generated.rs", @@ -837,8 +857,18 @@ }, { "path": "crates/libremetaverse/Cargo.toml", - "bytes": 1255, - "sha256": "478405c87bfc7f832d933aba6bd0b2e5a7c49ca3098152f55b25cc230a92f68a" + "bytes": 1312, + "sha256": "b411a01e523302cce48da0f79d4d37cf499c59ea378f048607b6720a051ea855" + }, + { + "path": "crates/libremetaverse/README.md", + "bytes": 12959, + "sha256": "22dc9242d755d6b81214081dc534c02bf84380c16d5f873729033dd04ec1d16c" + }, + { + "path": "crates/libremetaverse/assets/avatar_lad.xml", + "bytes": 354436, + "sha256": "6f97a1408fa8f0ab5c09043da433c701701fe075e8a97867fcbcda207adf9eaf" }, { "path": "crates/libremetaverse/examples/cancellation.rs", @@ -947,8 +977,8 @@ }, { "path": "crates/libremetaverse/src/avatar_rig.rs", - "bytes": 25122, - "sha256": "9de946d7971afd8fb0a821ce5ea369622390bb81b37677e17038f1737e9328c1" + "bytes": 25108, + "sha256": "a259cd61f183198131c1b5926da59d53d4e203cc263cd4d0ad780d0d93906a87" }, { "path": "crates/libremetaverse/src/baking_texture_provider.rs", @@ -1087,8 +1117,8 @@ }, { "path": "crates/libremetaverse/src/lib.rs", - "bytes": 10818, - "sha256": "a493e177551119790b8a39f434f9a0f3615330d4c4ca2051d860ebb1a64cb3fb" + "bytes": 10799, + "sha256": "7c533ad9b3a2d43fabf168b147d575997f22ad8684f51fd1010dd3cac6e96341" }, { "path": "crates/libremetaverse/src/login.rs", @@ -1340,6 +1370,11 @@ "bytes": 8816, "sha256": "6c79601aaef84b87d3378f90ec3772de0e5d274d834bf8f193a3a9c028ee18ce" }, + { + "path": "docs/release-artifacts.md", + "bytes": 4663, + "sha256": "4f8571ce3520d5aaf0ded8722330ab4892ba40b1092048c8f955fe623726f688" + }, { "path": "docs/release-ci-matrix.md", "bytes": 4072, @@ -1472,8 +1507,8 @@ }, { "path": "programs/Cargo.toml", - "bytes": 1738, - "sha256": "8e42579a829d88caa8b2dad7035f27d9de6c038d9692bd71122b23d242c07108" + "bytes": 1976, + "sha256": "1ebd9e4ee446945b6e7c6430f430eb52e29fd3a319b43caf49f45301adf6ea2d" }, { "path": "programs/README.md", @@ -1482,8 +1517,8 @@ }, { "path": "programs/build.rs", - "bytes": 1553, - "sha256": "b7f8d736c499b144f99e80c469db8b5713b4af79f16fa85c40d1b66395fb41d0" + "bytes": 444, + "sha256": "415f9f66d3ff3cf32fa7e8f8ad0557571d3b0e080ad6e0a028f0200670a00f08" }, { "path": "programs/src/bin/inventory_explorer.rs", @@ -2232,14 +2267,19 @@ }, { "path": "tools/ci-matrix/Cargo.toml", - "bytes": 375, - "sha256": "6d43e2721c77920cc6f4896ddaebf74302ed1751bdf2aa9b3dcd42dd9f82c652" + "bytes": 425, + "sha256": "a7dcbf021795dc977dc4e34a105b0360c527f8974440cdaa2a81923e3ceb5a9c" }, { "path": "tools/ci-matrix/src/api_surface.rs", "bytes": 48381, "sha256": "ec7ad0bcca094413eccdf5c76c62bf0ce63678bcedf355aad0c9b8a73156877b" }, + { + "path": "tools/ci-matrix/src/artifact.rs", + "bytes": 36939, + "sha256": "f1b56f753d77ea40e72d8a4288174fdbc5974285c13d07f04ea52b34dbdd7506" + }, { "path": "tools/ci-matrix/src/dependency.rs", "bytes": 12796, @@ -2247,23 +2287,23 @@ }, { "path": "tools/ci-matrix/src/documentation.rs", - "bytes": 21418, - "sha256": "046a7dae35ceba47fdc2cf04a7e3002896899bc654c046b53d32d84923557930" + "bytes": 22793, + "sha256": "7644465653cc3ac4ee2baa63e0157913f31b453964fbaa85d27d7ca8596e6b74" }, { "path": "tools/ci-matrix/src/lib.rs", - "bytes": 21318, - "sha256": "2c203fa0820825ce53d572a9883266b605a6661f84dcec10b226e5a0c7ce79cd" + "bytes": 21367, + "sha256": "ef3684bd5341a61dec6581ead48d3ccdfe79b040afa40854fa680fa1ba5af87a" }, { "path": "tools/ci-matrix/src/main.rs", - "bytes": 5620, - "sha256": "1328db04a3bced20fd0167b813017b4b4024e6eb77beeaf06f82959ec45145c4" + "bytes": 7108, + "sha256": "4b333b0a8e2744a043553d5bdc56e1eb8701b628e58e6290119bada0370d39df" }, { "path": "tools/ci-matrix/src/provenance.rs", - "bytes": 44234, - "sha256": "d9421c261ba99e68d1eba2c4606cbf63f8d9dc61b87d36f5a696afec1efeadb1" + "bytes": 44270, + "sha256": "6946295307a3740ba6b70187a37e0f4011f8070a4ef9e1adccecc3487aa71499" }, { "path": "tools/codegen/Cargo.toml", @@ -2364,13 +2404,13 @@ "generated_release_files": [ { "path": "release/DEPENDENCY-LICENSES.json", - "bytes": 215927, - "sha256": "1644915765f829b6476cfa65f2105f8dead39ca0e65640c87bfb497e5d873d0e" + "bytes": 217677, + "sha256": "d1a52489f855aa7b3f39731748e4df8cbce615bcd62384370d5113dc0e259eec" }, { "path": "release/THIRD-PARTY-NOTICES.md", - "bytes": 842933, - "sha256": "7d61809632c7e557331f03dcdf452c5fb777ee2df9644c8b0c7f32a0fc1f26fc" + "bytes": 848536, + "sha256": "de5088841682813cc952af4d1dcc57d2521694e192888042d5585e344e570d16" }, { "path": "release/NATIVE-LICENSES.md", @@ -2403,6 +2443,14 @@ "license": "CC-BY-SA-3.0", "distribution": "source-and-generated-data" }, + { + "path": "crates/libremetaverse/assets/avatar_lad.xml", + "sha256": "6f97a1408fa8f0ab5c09043da433c701701fe075e8a97867fcbcda207adf9eaf", + "kind": "linden-data-package-mirror", + "origin": "codegen/inputs/avatar_lad.xml", + "license": "CC-BY-SA-3.0", + "distribution": "source-package-runtime-data" + }, { "path": "codegen/inputs/avatar_skeleton.xml", "sha256": "addb3cc4cd582ed4343d778e06daf90c6b77b61b4ecb4a20d4bf202c2b93908a", diff --git a/release/THIRD-PARTY-NOTICES.md b/release/THIRD-PARTY-NOTICES.md index e074df7..8e5acad 100644 --- a/release/THIRD-PARTY-NOTICES.md +++ b/release/THIRD-PARTY-NOTICES.md @@ -116,6 +116,7 @@ This document must accompany source and binary distributions. A package's presen | `getrandom 0.4.3` | `MIT OR Apache-2.0` | `300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099` | `aaff376532ea30a0cd5330b9502ad4a4c8bf769c539c87ffe78819d188a18ebf`
`523a42c25d245dde9c015f882cec7f4555aad883382a6cf19b4b7d9b2cd5419b` | | `ghash 0.5.1` | `Apache-2.0 OR MIT` | `f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1` | `a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5`
`c0fabcc308b450cb6fe62c9f56fae6abbeb9768eb80d992484c857d744846c2e` | | `glob 0.3.4` | `MIT OR Apache-2.0` | `e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b` | `a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2`
`6485b8ed310d3f0340bf1ad1f47645069ce4069dcc6bb46c7d5c6faf41de1fdb` | +| `goblin 0.10.5` | `MIT` | `983a6aafb3b12d4c41ea78d39e189af4298ce747353945ff5105b54a056e5cd9` | `655e3ee7a4c27430774962e62a6d37d7348e5f2f292010ad674ce1bebefd24bc` | | `group 0.13.0` | `MIT/Apache-2.0` | `f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63` | `7429802d3e67e3308471b91fbea341e484c4e0a23d0fb230a8636c38184cec79`
`a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2`
`23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3` | | `hashbrown 0.17.1` | `MIT OR Apache-2.0` | `ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a` | `a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2`
`ff8f68cb076caf8cefe7a6430d4ac086ce6af2ca8ce2c4e5a2004d4552ef52a2` | | `heck 0.5.0` | `MIT OR Apache-2.0` | `2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea` | `a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2`
`7b63ecd5f1902af1b63729947373683c32745c16a10e8e6292e2e2dcd7e90ae0` | @@ -214,6 +215,7 @@ This document must accompany source and binary distributions. A package's presen | `pin-project-lite 0.2.17` | `Apache-2.0 OR MIT` | `a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd` | `0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594`
`23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3` | | `pkcs8 0.10.2` | `Apache-2.0 OR MIT` | `f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7` | `a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5`
`ad64fcb9589f162720f3cc5010ad76ca6ad3764e11861f9192c489df176bb71d` | | `pkg-config 0.3.33` | `MIT OR Apache-2.0` | `19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e` | `a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2`
`378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397` | +| `plain 0.2.3` | `MIT/Apache-2.0` | `b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6` | `a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2`
`bc12b75fd81829814d843a03fa52aad0e53355b1f13665e309ff7fa33c66e5b5` | | `poly1305 0.8.0` | `Apache-2.0 OR MIT` | `8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf` | `a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5`
`6d99fffea918d1f7dfe567029c7bd21d6fcaf310ad28c44e6511a0e1f68c79ea` | | `polyval 0.6.2` | `Apache-2.0 OR MIT` | `9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25` | `a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5`
`a291b6910744d262a9e953c3b5868d74510e20161359f649a11e1791fde84fcd` | | `potential_utf 0.1.5` | `Unicode-3.0` | `0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564` | `f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2` | @@ -257,6 +259,8 @@ This document must accompany source and binary distributions. A package's presen | `rustversion 1.0.23` | `MIT OR Apache-2.0` | `cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f` | `62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a`
`23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3` | | `same-file 1.0.6` | `Unlicense/MIT` | `93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502` | `01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f`
`cb3c929a05e6cbc9de9ab06a4c57eeb60ca8c724bef6c138c87d3a577e27aa14`
`7e12e5df4bae12cb21581ba157ced20e1986a0508dd10d0e8a4ab9a4cf94e85c` | | `schannel 0.1.29` | `MIT` | `91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939` | `aa72991ac35b4de0034da0afe943e62b48c4092fc2ba13ae47806d8e9a4ad551` | +| `scroll 0.13.0` | `MIT` | `c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add` | `6e24b7455f0b9afefdf4f3efd59a56ce76a3020c2dc4371937e281fc5e587fd7` | +| `scroll_derive 0.13.1` | `MIT` | `ed76efe62313ab6610570951494bdaa81568026e0318eaa55f167de70eeea67d` | `b35d026cc7aca9d5859a02eb87ddf7a386a24c986838651bd1f283f94e003327` | | `sctp-proto 0.10.3` | `MIT OR Apache-2.0` | `f895c3c33ae20283f9129bd09db2ca69138798b372ef2b98bd2946d23ea4819b` | `814d11eba59f964bca7e74ef94f0d1eff1d5f322f895fb9b5d7e06f77debf3b2`
`7a6631daa22a9f1772e4a474ede9c3374a6c7a89b61c8b5973f7fd5ccd5cee8c` | | `sec1 0.7.3` | `Apache-2.0 OR MIT` | `d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc` | `a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5`
`4a883ecc3bb1010faed542bf63d53e530fea5e5e12cf676aed588784298ba929` | | `security-framework 3.7.0` | `MIT OR Apache-2.0` | `b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d` | `a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2`
`91e934255ba3b2f21103d68c5581c23ef34aa95c4628e4405b8c901935e11c69` | @@ -6737,6 +6741,36 @@ DEALINGS IN THE SOFTWARE. +### `655e3ee7a4c27430774962e62a6d37d7348e5f2f292010ad674ce1bebefd24bc` + +Packages: goblin 0.10.5
+Source filenames: LICENSE + +
+The MIT License (MIT)
+
+Copyright (c) m4b 2016-2024
+
+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.
+
+
+ ### `65f94e99ddaf4f5d1782a6dae23f35d4293a9a01444a13135a6887017d353cee` Packages: bumpalo 3.20.3
@@ -7226,6 +7260,36 @@ APPENDIX: How to apply the Apache License to your work. +### `6e24b7455f0b9afefdf4f3efd59a56ce76a3020c2dc4371937e281fc5e587fd7` + +Packages: scroll 0.13.0
+Source filenames: LICENSE + +
+The MIT License (MIT)
+
+Copyright (c) m4b 2016
+
+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.
+
+
+ ### `6efb0476a1cc085077ed49357026d8c173bf33017278ef440f222fb9cbcb66e6` Packages: anstream 1.0.0, anstyle 1.0.14, anstyle-parse 1.0.0, anstyle-query 1.1.5, anstyle-wincon 3.0.11, clap 4.6.6, clap_builder 4.6.6, clap_derive 4.6.4, clap_lex 1.1.0, colorchoice 1.0.5, is_terminal_polyfill 1.70.2, once_cell_polyfill 1.70.2, serde_spanned 1.1.1, toml 1.1.4+spec-1.1.0, toml_datetime 1.1.1+spec-1.1.0, toml_edit 0.25.13+spec-1.1.0, toml_parser 1.1.3+spec-1.1.0, toml_writer 1.1.2+spec-1.1.0
@@ -10914,7 +10978,7 @@ DEALINGS IN THE SOFTWARE. ### `a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2` -Packages: arrayvec 0.7.8, asn1-rs 0.7.2, asn1-rs-derive 0.6.0, asn1-rs-impl 0.2.0, atomic-waker 1.1.2, autocfg 1.5.1, base64 0.22.1, bitflags 2.13.1, bumpalo 3.20.3, cc 1.4.2, cexpr 0.6.0, cfg-if 1.0.4, cmake 0.1.58, core-foundation 0.10.1, core-foundation-sys 0.8.7, coreaudio-rs 0.14.2, curve25519-dalek-derive 0.1.1, der-parser 10.0.0, displaydoc 0.2.7, either 1.17.0, equivalent 1.0.2, errno 0.3.14, fastrand 2.5.0, filetime 0.2.29, find-msvc-tools 0.1.10, flate2 1.1.9, form_urlencoded 1.2.2, glob 0.3.4, group 0.13.0, hashbrown 0.17.1, heck 0.5.0, httparse 1.10.1, hyper-rustls 0.27.9, idna 1.1.0, idna_adapter 1.2.2, indexmap 2.14.0, is 0.11.0, itertools 0.13.0, jobserver 0.1.35, js-sys 0.3.104, lazy_static 1.5.0, linux-raw-sys 0.12.1, log 0.4.33, num-bigint 0.4.8, num-derive 0.4.2, num-integer 0.1.46, num-traits 0.2.19, oid-registry 0.8.1, once_cell 1.21.4, openssl-probe 0.2.1, percent-encoding 2.3.2, pkg-config 0.3.33, regex 1.13.1, regex-automata 0.4.18, regex-syntax 0.8.11, roxmltree 0.21.1, rustc_version 0.4.1, rusticata-macros 4.1.0, rustix 1.1.4, rustls 0.23.43, rustls-native-certs 0.8.4, security-framework 3.7.0, security-framework-sys 2.17.0, signal-hook-registry 1.4.8, simd_cesu8 1.2.0, smallvec 1.15.2, socket2 0.6.5, stable_deref_trait 1.2.1, str0m 0.22.0, str0m-proto 0.7.0, str0m-rust-crypto 0.6.0, tar 0.4.46, url 2.5.8, uuid 1.24.0, version_check 0.9.5, wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12, wasm-bindgen 0.2.127, wasm-bindgen-futures 0.4.77, wasm-bindgen-macro 0.2.127, wasm-bindgen-macro-support 0.2.127, wasm-bindgen-shared 0.2.127, web-sys 0.3.104, wit-bindgen 0.57.1, x509-parser 0.18.1, xattr 1.6.1, yasna 0.6.0
+Packages: arrayvec 0.7.8, asn1-rs 0.7.2, asn1-rs-derive 0.6.0, asn1-rs-impl 0.2.0, atomic-waker 1.1.2, autocfg 1.5.1, base64 0.22.1, bitflags 2.13.1, bumpalo 3.20.3, cc 1.4.2, cexpr 0.6.0, cfg-if 1.0.4, cmake 0.1.58, core-foundation 0.10.1, core-foundation-sys 0.8.7, coreaudio-rs 0.14.2, curve25519-dalek-derive 0.1.1, der-parser 10.0.0, displaydoc 0.2.7, either 1.17.0, equivalent 1.0.2, errno 0.3.14, fastrand 2.5.0, filetime 0.2.29, find-msvc-tools 0.1.10, flate2 1.1.9, form_urlencoded 1.2.2, glob 0.3.4, group 0.13.0, hashbrown 0.17.1, heck 0.5.0, httparse 1.10.1, hyper-rustls 0.27.9, idna 1.1.0, idna_adapter 1.2.2, indexmap 2.14.0, is 0.11.0, itertools 0.13.0, jobserver 0.1.35, js-sys 0.3.104, lazy_static 1.5.0, linux-raw-sys 0.12.1, log 0.4.33, num-bigint 0.4.8, num-derive 0.4.2, num-integer 0.1.46, num-traits 0.2.19, oid-registry 0.8.1, once_cell 1.21.4, openssl-probe 0.2.1, percent-encoding 2.3.2, pkg-config 0.3.33, plain 0.2.3, regex 1.13.1, regex-automata 0.4.18, regex-syntax 0.8.11, roxmltree 0.21.1, rustc_version 0.4.1, rusticata-macros 4.1.0, rustix 1.1.4, rustls 0.23.43, rustls-native-certs 0.8.4, security-framework 3.7.0, security-framework-sys 2.17.0, signal-hook-registry 1.4.8, simd_cesu8 1.2.0, smallvec 1.15.2, socket2 0.6.5, stable_deref_trait 1.2.1, str0m 0.22.0, str0m-proto 0.7.0, str0m-rust-crypto 0.6.0, tar 0.4.46, url 2.5.8, uuid 1.24.0, version_check 0.9.5, wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12, wasm-bindgen 0.2.127, wasm-bindgen-futures 0.4.77, wasm-bindgen-macro 0.2.127, wasm-bindgen-macro-support 0.2.127, wasm-bindgen-shared 0.2.127, web-sys 0.3.104, wit-bindgen 0.57.1, x509-parser 0.18.1, xattr 1.6.1, yasna 0.6.0
Source filenames: LICENSE-APACHE, repository-license/asn1-rs-0.7.2/LICENSE-APACHE, repository-license/str0m-0.22.0/src/packet/LICENSE-APACHE.txt, src/packet/LICENSE-APACHE.txt
@@ -12189,6 +12253,36 @@ DEALINGS IN THE SOFTWARE.
 
 
+### `b35d026cc7aca9d5859a02eb87ddf7a386a24c986838651bd1f283f94e003327` + +Packages: scroll_derive 0.13.1
+Source filenames: LICENSE + +
+MIT License
+
+Copyright (c) 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.
+
+
+ ### `b38f11f6096706e6de553dabe2a7ed142d59b6fa8c97e290c67496154745cdd5` Packages: idna 1.1.0, percent-encoding 2.3.2, url 2.5.8
@@ -12878,6 +12972,40 @@ The published crate archive contains no discoverable top-level license/notice fi +### `bc12b75fd81829814d843a03fa52aad0e53355b1f13665e309ff7fa33c66e5b5` + +Packages: plain 0.2.3
+Source filenames: LICENSE-MIT + +
+Copyright (c) 2017 Plain 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.
+
+
+ ### `bdebaf9156a298f8fdab56dd26cb5144673de522d80f4c0d88e0039145f147f9` Packages: ecdsa 0.16.9, rfc6979 0.4.0
diff --git a/tools/ci-matrix/Cargo.toml b/tools/ci-matrix/Cargo.toml index c7cc6a8..b6135ea 100644 --- a/tools/ci-matrix/Cargo.toml +++ b/tools/ci-matrix/Cargo.toml @@ -9,9 +9,12 @@ description = "Validated release CI matrix runner for MetaCrate" publish = false [dependencies] +flate2 = "1.1.2" +goblin = "0.10.5" serde = { version = "1", features = ["derive"] } serde_json = "1" sha2 = "0.11" +tar = "0.4.46" [lints] workspace = true diff --git a/tools/ci-matrix/src/artifact.rs b/tools/ci-matrix/src/artifact.rs new file mode 100644 index 0000000..a48b35d --- /dev/null +++ b/tools/ci-matrix/src/artifact.rs @@ -0,0 +1,1145 @@ +//! Release-package, native-binary, and clean-room runtime audit. + +#![allow(clippy::too_many_lines)] + +use super::{MatrixError, Result}; +use flate2::read::GzDecoder; +use goblin::{Object, mach}; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use std::collections::{BTreeMap, BTreeSet}; +use std::ffi::OsStr; +use std::fmt::Write as _; +use std::fs::{self, OpenOptions}; +use std::io::{Read, Write}; +use std::path::{Component, Path, PathBuf}; +use std::process::{Command, Stdio}; +use std::thread; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +use tar::Archive; + +const POLICY_PATH: &str = "ci/artifact-policy.json"; +const MAX_ARCHIVE_BYTES: u64 = 256 * 1024 * 1024; +const MAX_ENTRY_BYTES: u64 = 128 * 1024 * 1024; +const MAX_BINARY_BYTES: u64 = 512 * 1024 * 1024; +const SMOKE_TIMEOUT: Duration = Duration::from_secs(30); +const CREDENTIAL_ENV: [&str; 9] = [ + "GRID_USER", + "GRID_FIRST_NAME", + "GRID_LAST_NAME", + "GRID_PASSWORD", + "GRID_LOGIN_URL", + "GRID_MASTER_UUID", + "IRC_HOST", + "IRC_PORT", + "IRC_CHANNEL", +]; + +#[derive(Deserialize)] +struct Policy { + schema: u32, + version: String, + release_packages: Vec, + binaries: Vec, + offline_smokes: Vec, + allowed_native_ffi: Vec, + mirrored_inputs: Vec, + forbidden_dependencies: Vec, + forbidden_artifact_strings: Vec, + forbidden_source_strings: Vec, +} + +#[derive(Deserialize)] +struct BinaryPolicy { + name: String, + services: Vec, +} + +#[derive(Deserialize)] +struct SmokePolicy { + binary: String, + args: Vec, + required_output: bool, +} + +#[derive(Deserialize)] +struct MirroredInput { + source: String, + package_path: String, +} + +#[derive(Deserialize)] +struct Metadata { + packages: Vec, + workspace_members: Vec, + resolve: Option, +} + +#[derive(Deserialize)] +struct MetadataPackage { + id: String, + name: String, + version: String, + manifest_path: PathBuf, + dependencies: Vec, + targets: Vec, +} + +#[derive(Deserialize)] +struct MetadataDependency { + name: String, + req: String, + path: Option, +} + +#[derive(Deserialize)] +struct MetadataTarget { + name: String, + kind: Vec, +} + +#[derive(Deserialize)] +struct Resolve { + nodes: Vec, +} + +#[derive(Deserialize)] +struct ResolveNode { + id: String, + dependencies: Vec, +} + +#[derive(Serialize)] +struct Evidence { + schema: u32, + source_commit: String, + rustc: String, + operating_system: String, + architecture: String, + cargo_graph_packages: usize, + release_packages: Vec, + binaries: Vec, + clean_package_build: bool, + reference_checkout_required: bool, + helper_processes_invoked: Vec, + forbidden_findings: usize, +} + +#[derive(Serialize)] +struct PackageEvidence { + name: String, + version: String, + archive: String, + sha256: String, + files: usize, + unpacked_bytes: u64, +} + +#[derive(Serialize)] +struct BinaryEvidence { + name: String, + format: String, + bytes: u64, + sha256: String, + linked_libraries: Vec, + imported_symbols: usize, + services: Vec, + smokes: Vec, +} + +#[derive(Serialize)] +struct SmokeEvidence { + arguments: Vec, + exit_code: i32, + output_created: bool, +} + +struct InspectedBinary { + format: String, + libraries: BTreeSet, + imported_symbols: usize, +} + +/// Audits the complete shipped package and executable surface. +/// +/// # Errors +/// +/// Fails on metadata drift, unversioned internal dependencies, forbidden +/// runtime/bridge material, unsafe package paths, missing binaries, invalid +/// native objects, smoke failures, helper-process invocation, or a clean-room +/// package build failure. Existing evidence is never overwritten. +pub fn audit_artifacts( + root: &Path, + artifact_dir: &Path, + package_dir: &Path, + evidence_path: &Path, +) -> Result<()> { + if evidence_path.exists() { + return Err(MatrixError::new(format!( + "{} already exists; preserve or remove it before rerunning the audit", + evidence_path.display() + ))); + } + let policy: Policy = serde_json::from_slice(&fs::read(root.join(POLICY_PATH))?)?; + validate_policy(&policy)?; + let metadata = cargo_metadata(root)?; + let release = validate_metadata(root, &metadata, &policy)?; + audit_source(root, &release, &policy)?; + let (packages, extracted) = inspect_packages(package_dir, &policy)?; + let clean_package_build = build_extracted_packages(root, &extracted, &policy)?; + let binaries = inspect_and_smoke_binaries(root, artifact_dir, &policy)?; + let evidence = Evidence { + schema: 1, + source_commit: command_output(root, "git", &["rev-parse", "--verify", "HEAD"])?, + rustc: command_output(root, "rustc", &["--version"])?, + operating_system: std::env::consts::OS.into(), + architecture: std::env::consts::ARCH.into(), + cargo_graph_packages: metadata.packages.len(), + release_packages: packages, + binaries, + clean_package_build, + reference_checkout_required: false, + helper_processes_invoked: Vec::new(), + forbidden_findings: 0, + }; + write_new_json(evidence_path, &evidence) +} + +fn validate_policy(policy: &Policy) -> Result<()> { + if policy.schema != 1 || policy.version != "0.0.1" { + return Err(MatrixError::new("invalid artifact policy header")); + } + unique_nonempty(&policy.release_packages, "release package")?; + unique_nonempty(&policy.allowed_native_ffi, "native FFI path")?; + if policy.mirrored_inputs.is_empty() + || policy + .mirrored_inputs + .iter() + .any(|input| input.source.trim().is_empty() || input.package_path.trim().is_empty()) + { + return Err(MatrixError::new("invalid mirrored package input policy")); + } + unique_nonempty(&policy.forbidden_dependencies, "forbidden dependency")?; + unique_nonempty( + &policy.forbidden_artifact_strings, + "forbidden artifact string", + )?; + unique_nonempty(&policy.forbidden_source_strings, "forbidden source string")?; + let binary_names = policy + .binaries + .iter() + .map(|item| item.name.as_str()) + .collect::>(); + if binary_names.len() != policy.binaries.len() + || policy + .binaries + .iter() + .any(|item| item.name.trim().is_empty() || item.services.is_empty()) + { + return Err(MatrixError::new("invalid or duplicate binary policy")); + } + if policy.offline_smokes.iter().any(|smoke| { + !binary_names.contains(smoke.binary.as_str()) + || smoke.args.is_empty() + || smoke.args.iter().any(String::is_empty) + }) { + return Err(MatrixError::new("invalid offline smoke policy")); + } + Ok(()) +} + +fn unique_nonempty(values: &[String], label: &str) -> Result<()> { + let unique = values.iter().map(String::as_str).collect::>(); + if unique.len() != values.len() || values.iter().any(|value| value.trim().is_empty()) { + return Err(MatrixError::new(format!("invalid or duplicate {label}"))); + } + Ok(()) +} + +fn cargo_metadata(root: &Path) -> Result { + let output = Command::new("cargo") + .args(["metadata", "--format-version", "1", "--locked"]) + .current_dir(root) + .output()?; + if !output.status.success() { + return Err(MatrixError::new(format!( + "cargo metadata failed: {}", + String::from_utf8_lossy(&output.stderr).trim() + ))); + } + Ok(serde_json::from_slice(&output.stdout)?) +} + +fn validate_metadata<'a>( + root: &Path, + metadata: &'a Metadata, + policy: &Policy, +) -> Result> { + let workspace_members = metadata + .workspace_members + .iter() + .map(String::as_str) + .collect::>(); + let release_root = root.canonicalize()?.join("crates"); + let programs_manifest = root.canonicalize()?.join("programs/Cargo.toml"); + let discovered = metadata + .packages + .iter() + .filter(|package| workspace_members.contains(package.id.as_str())) + .filter_map(|package| { + let manifest = package.manifest_path.canonicalize().ok()?; + (manifest.starts_with(&release_root) || manifest == programs_manifest) + .then_some(package.name.as_str()) + }) + .collect::>(); + let declared = policy + .release_packages + .iter() + .map(String::as_str) + .collect::>(); + if discovered != declared { + return Err(MatrixError::new(format!( + "release package policy mismatch; expected workspace release surface {discovered:?}, found {declared:?}" + ))); + } + let by_name = metadata + .packages + .iter() + .map(|package| (package.name.as_str(), package)) + .collect::>(); + let mut release = Vec::new(); + for name in &policy.release_packages { + let package = by_name + .get(name.as_str()) + .copied() + .ok_or_else(|| MatrixError::new(format!("release package {name} is missing")))?; + if package.version != policy.version { + return Err(MatrixError::new(format!( + "release package {name} has version {}, expected {}", + package.version, policy.version + ))); + } + for dependency in &package.dependencies { + if dependency.path.is_some() + && (dependency.req == "*" || dependency.req.trim().is_empty()) + { + return Err(MatrixError::new(format!( + "release package {name} has unversioned path dependency {}", + dependency.name + ))); + } + } + let manifest = package.manifest_path.canonicalize()?; + if !manifest.starts_with(root.canonicalize()?) { + return Err(MatrixError::new(format!( + "release package {name} escapes the workspace" + ))); + } + release.push(package); + } + validate_binary_targets(&release, policy)?; + validate_dependency_closure(metadata, &release, policy)?; + Ok(release) +} + +fn validate_binary_targets(release: &[&MetadataPackage], policy: &Policy) -> Result<()> { + let actual = release + .iter() + .flat_map(|package| &package.targets) + .filter(|target| target.kind.iter().any(|kind| kind == "bin")) + .map(|target| target.name.as_str()) + .collect::>(); + let expected = policy + .binaries + .iter() + .map(|binary| binary.name.as_str()) + .collect::>(); + if actual != expected { + return Err(MatrixError::new(format!( + "release binary policy mismatch; expected {expected:?}, found {actual:?}" + ))); + } + Ok(()) +} + +fn validate_dependency_closure( + metadata: &Metadata, + release: &[&MetadataPackage], + policy: &Policy, +) -> Result<()> { + let resolve = metadata + .resolve + .as_ref() + .ok_or_else(|| MatrixError::new("cargo metadata has no dependency resolution"))?; + let edges = resolve + .nodes + .iter() + .map(|node| (node.id.as_str(), node.dependencies.as_slice())) + .collect::>(); + let names = metadata + .packages + .iter() + .map(|package| (package.id.as_str(), package.name.as_str())) + .collect::>(); + let forbidden = policy + .forbidden_dependencies + .iter() + .map(|name| name.to_ascii_lowercase()) + .collect::>(); + let mut pending = release + .iter() + .map(|package| package.id.as_str()) + .collect::>(); + let mut visited = BTreeSet::new(); + while let Some(id) = pending.pop() { + if !visited.insert(id) { + continue; + } + let name = names + .get(id) + .ok_or_else(|| MatrixError::new(format!("unknown dependency package ID {id}")))?; + if forbidden.contains(&name.to_ascii_lowercase()) { + return Err(MatrixError::new(format!( + "release dependency graph contains forbidden runtime {name}" + ))); + } + if let Some(dependencies) = edges.get(id) { + pending.extend(dependencies.iter().map(String::as_str)); + } + } + if metadata.workspace_members.is_empty() { + return Err(MatrixError::new("cargo workspace has no members")); + } + Ok(()) +} + +fn audit_source(root: &Path, release: &[&MetadataPackage], policy: &Policy) -> Result<()> { + for input in &policy.mirrored_inputs { + let source = fs::read(root.join(&input.source))?; + let package = fs::read(root.join(&input.package_path))?; + if source != package { + return Err(MatrixError::new(format!( + "package mirror {} differs from {}", + input.package_path, input.source + ))); + } + } + let ffi_allow = policy + .allowed_native_ffi + .iter() + .map(String::as_str) + .collect::>(); + let mut visited = BTreeSet::new(); + for package in release { + let directory = package + .manifest_path + .parent() + .ok_or_else(|| MatrixError::new("package manifest has no parent"))?; + walk_source(root, directory, &mut visited, &ffi_allow, policy)?; + } + Ok(()) +} + +fn walk_source( + root: &Path, + directory: &Path, + visited: &mut BTreeSet, + ffi_allow: &BTreeSet<&str>, + policy: &Policy, +) -> Result<()> { + let mut entries = fs::read_dir(directory)?.collect::>>()?; + entries.sort_by_key(std::fs::DirEntry::file_name); + for entry in entries { + let file_type = entry.file_type()?; + if file_type.is_symlink() { + return Err(MatrixError::new(format!( + "release source contains symlink {}", + entry.path().display() + ))); + } + if file_type.is_dir() { + let name = entry.file_name(); + if name != OsStr::new("target") && name != OsStr::new(".git") { + walk_source(root, &entry.path(), visited, ffi_allow, policy)?; + } + } else if file_type.is_file() + && matches!( + entry.path().extension().and_then(OsStr::to_str), + Some("rs" | "toml") + ) + { + let path = entry.path(); + if !visited.insert(path.clone()) { + continue; + } + let relative = relative(root, &path)?; + let text = fs::read_to_string(&path)?; + for forbidden in &policy.forbidden_source_strings { + if text.contains(forbidden) { + return Err(MatrixError::new(format!( + "release source {relative} contains forbidden runtime hook {forbidden:?}" + ))); + } + } + if text.contains("extern \"C\"") && !ffi_allow.contains(relative.as_str()) { + return Err(MatrixError::new(format!( + "unreviewed native/generated FFI boundary in {relative}" + ))); + } + } + } + Ok(()) +} + +fn inspect_packages( + package_dir: &Path, + policy: &Policy, +) -> Result<(Vec, ScratchDir)> { + if !package_dir.is_dir() { + return Err(MatrixError::new(format!( + "package directory {} is missing", + package_dir.display() + ))); + } + let scratch = ScratchDir::new("metacrate-package-audit")?; + let mut evidence = Vec::new(); + for name in &policy.release_packages { + let archive_name = format!("{name}-{}.crate", policy.version); + let archive_path = package_dir.join(&archive_name); + let metadata = fs::metadata(&archive_path).map_err(|error| { + MatrixError::new(format!("missing package {archive_name}: {error}")) + })?; + if metadata.len() > MAX_ARCHIVE_BYTES { + return Err(MatrixError::new(format!( + "package {archive_name} exceeds the archive limit" + ))); + } + let (files, bytes) = inspect_and_extract_archive(&archive_path, &scratch.path, policy)?; + evidence.push(PackageEvidence { + name: name.clone(), + version: policy.version.clone(), + archive: archive_name, + sha256: hash_file(&archive_path)?, + files, + unpacked_bytes: bytes, + }); + } + let actual = fs::read_dir(package_dir)? + .filter_map(std::result::Result::ok) + .filter(|entry| entry.path().extension() == Some(OsStr::new("crate"))) + .map(|entry| entry.file_name().to_string_lossy().into_owned()) + .collect::>(); + let expected = evidence + .iter() + .map(|item| item.archive.clone()) + .collect::>(); + if actual != expected { + return Err(MatrixError::new(format!( + "package directory contains missing or unexpected archives; expected {expected:?}, found {actual:?}" + ))); + } + Ok((evidence, scratch)) +} + +fn inspect_and_extract_archive( + archive_path: &Path, + scratch: &Path, + policy: &Policy, +) -> Result<(usize, u64)> { + let file = fs::File::open(archive_path)?; + let mut archive = Archive::new(GzDecoder::new(file)); + let mut files = 0; + let mut total = 0_u64; + let forbidden = policy + .forbidden_artifact_strings + .iter() + .map(|value| value.to_ascii_lowercase()) + .collect::>(); + for entry in archive.entries()? { + let mut entry = entry?; + let path = entry.path()?.into_owned(); + validate_archive_path(&path)?; + let entry_type = entry.header().entry_type(); + if !entry_type.is_file() && !entry_type.is_dir() { + return Err(MatrixError::new(format!( + "unsupported link or special entry in source package {}", + path.display() + ))); + } + let size = entry.size(); + if size > MAX_ENTRY_BYTES { + return Err(MatrixError::new(format!( + "package entry {} exceeds the size limit", + path.display() + ))); + } + total = total + .checked_add(size) + .ok_or_else(|| MatrixError::new("package size overflow"))?; + if entry_type.is_file() { + reject_managed_extension(&path)?; + let mut contents = Vec::with_capacity(usize::try_from(size).unwrap_or(0)); + entry.read_to_end(&mut contents)?; + reject_forbidden_bytes(&contents, &forbidden, &path)?; + if path.file_name() == Some(OsStr::new("Cargo.toml")) { + reject_packaged_path_dependencies(&contents, &path)?; + } + let output = scratch.join(&path); + if let Some(parent) = output.parent() { + fs::create_dir_all(parent)?; + } + fs::write(&output, &contents)?; + files += 1; + } + } + Ok((files, total)) +} + +fn reject_packaged_path_dependencies(bytes: &[u8], path: &Path) -> Result<()> { + let manifest = std::str::from_utf8(bytes).map_err(|_| { + MatrixError::new(format!("packaged manifest {} is not UTF-8", path.display())) + })?; + let mut dependency_section = false; + for line in manifest.lines() { + let trimmed = line.trim(); + if trimmed.starts_with('[') && trimmed.ends_with(']') { + let section = trimmed.trim_matches(['[', ']']); + dependency_section = section + .split('.') + .any(|component| component.ends_with("dependencies")); + } else if dependency_section && trimmed.contains("path =") { + return Err(MatrixError::new(format!( + "packaged manifest {} retains a workspace path dependency", + path.display() + ))); + } + } + Ok(()) +} + +fn validate_archive_path(path: &Path) -> Result<()> { + if path.is_absolute() + || path.components().any(|component| { + matches!( + component, + Component::ParentDir | Component::RootDir | Component::Prefix(_) + ) + }) + { + return Err(MatrixError::new(format!( + "unsafe package archive path {}", + path.display() + ))); + } + Ok(()) +} + +fn reject_managed_extension(path: &Path) -> Result<()> { + let extension = path + .extension() + .and_then(OsStr::to_str) + .map(str::to_ascii_lowercase); + if extension.as_deref().is_some_and(|value| { + matches!( + value, + "cs" | "csproj" | "dll" | "exe" | "pdb" | "nupkg" | "jar" | "class" + ) + }) { + return Err(MatrixError::new(format!( + "managed/foreign executable material in source package {}", + path.display() + ))); + } + Ok(()) +} + +fn reject_forbidden_bytes(bytes: &[u8], forbidden: &[String], path: &Path) -> Result<()> { + let lower = String::from_utf8_lossy(bytes).to_ascii_lowercase(); + if let Some(found) = forbidden + .iter() + .find(|value| lower.contains(value.as_str())) + { + return Err(MatrixError::new(format!( + "forbidden runtime marker {found:?} in {}", + path.display() + ))); + } + Ok(()) +} + +fn build_extracted_packages(root: &Path, scratch: &ScratchDir, policy: &Policy) -> Result { + let members = policy + .release_packages + .iter() + .map(|name| format!("\"{name}-{}\"", policy.version)) + .collect::>() + .join(",\n "); + let patches = policy + .release_packages + .iter() + .map(|name| format!("{name} = {{ path = \"{name}-{}\" }}", policy.version)) + .collect::>() + .join("\n"); + let manifest = format!( + "[workspace]\nresolver = \"3\"\nmembers = [\n {members}\n]\n\n[patch.crates-io]\n{patches}\n" + ); + fs::write(scratch.path.join("Cargo.toml"), manifest)?; + let target = scratch.path.join("target"); + let status = Command::new("cargo") + .args([ + "check", + "--workspace", + "--lib", + "--bins", + "--offline", + "-j", + "1", + ]) + .arg("--target-dir") + .arg(&target) + .current_dir(&scratch.path) + .env("CARGO_INCREMENTAL", "0") + .env("CARGO_PROFILE_DEV_DEBUG", "0") + .env( + "METACRATE_REFERENCE_ROOT", + root.join("reference-checkout-must-not-exist"), + ) + .status()?; + if !status.success() { + return Err(MatrixError::new( + "extracted release packages failed to build without the reference checkout", + )); + } + Ok(true) +} + +fn inspect_and_smoke_binaries( + root: &Path, + artifact_dir: &Path, + policy: &Policy, +) -> Result> { + let trap = HelperTrap::new()?; + let forbidden = policy + .forbidden_artifact_strings + .iter() + .map(|value| value.to_ascii_lowercase()) + .collect::>(); + let mut evidence = Vec::new(); + for binary_policy in &policy.binaries { + let path = binary_path(artifact_dir, &binary_policy.name); + let metadata = fs::metadata(&path).map_err(|error| { + MatrixError::new(format!( + "missing release binary {}: {error}", + path.display() + )) + })?; + if !metadata.is_file() || metadata.len() > MAX_BINARY_BYTES { + return Err(MatrixError::new(format!( + "invalid or oversized release binary {}", + path.display() + ))); + } + let bytes = fs::read(&path)?; + reject_forbidden_bytes(&bytes, &forbidden, &path)?; + let inspected = inspect_object(&bytes, &path, &forbidden)?; + let mut smokes = vec![run_smoke(root, &path, &["--help".into()], false, &trap)?]; + for smoke in policy + .offline_smokes + .iter() + .filter(|smoke| smoke.binary == binary_policy.name) + { + smokes.push(run_smoke( + root, + &path, + &smoke.args, + smoke.required_output, + &trap, + )?); + } + evidence.push(BinaryEvidence { + name: binary_policy.name.clone(), + format: inspected.format, + bytes: metadata.len(), + sha256: hash_bytes(&bytes), + linked_libraries: inspected.libraries.into_iter().collect(), + imported_symbols: inspected.imported_symbols, + services: binary_policy.services.clone(), + smokes, + }); + } + trap.verify_clean()?; + Ok(evidence) +} + +fn binary_path(directory: &Path, name: &str) -> PathBuf { + #[cfg(windows)] + { + directory.join(format!("{name}.exe")) + } + #[cfg(not(windows))] + { + directory.join(name) + } +} + +fn inspect_object(bytes: &[u8], path: &Path, forbidden: &[String]) -> Result { + let object = Object::parse(bytes).map_err(|error| { + MatrixError::new(format!("parse native object {}: {error}", path.display())) + })?; + let mut libraries = BTreeSet::new(); + let mut symbols = Vec::new(); + let format = match object { + Object::Elf(elf) => { + libraries.extend(elf.libraries.into_iter().map(str::to_owned)); + symbols.extend( + elf.dynsyms + .iter() + .filter_map(|symbol| elf.dynstrtab.get_at(symbol.st_name)) + .map(str::to_owned), + ); + "ELF" + } + Object::PE(pe) => { + libraries.extend(pe.libraries.into_iter().map(str::to_owned)); + symbols.extend( + pe.imports + .into_iter() + .map(|import| import.name.into_owned()), + ); + "PE" + } + Object::Mach(mach::Mach::Binary(binary)) => { + libraries.extend(binary.libs.iter().copied().map(str::to_owned)); + symbols.extend( + binary + .symbols() + .filter_map(std::result::Result::ok) + .map(|(name, _)| name.to_owned()), + ); + "Mach-O" + } + Object::Mach(mach::Mach::Fat(fat)) => { + for architecture in &fat { + if let mach::SingleArch::MachO(binary) = architecture.map_err(|error| { + MatrixError::new(format!("parse fat Mach-O {}: {error}", path.display())) + })? { + libraries.extend(binary.libs.iter().copied().map(str::to_owned)); + symbols.extend( + binary + .symbols() + .filter_map(std::result::Result::ok) + .map(|(name, _)| name.to_owned()), + ); + } + } + "Mach-O-fat" + } + _ => { + return Err(MatrixError::new(format!( + "{} is not an ELF, PE, or Mach-O executable", + path.display() + ))); + } + }; + for value in libraries.iter().chain(symbols.iter()) { + let lower = value.to_ascii_lowercase(); + if let Some(found) = forbidden.iter().find(|item| lower.contains(item.as_str())) { + return Err(MatrixError::new(format!( + "native object {} references forbidden runtime marker {found:?} through {value:?}", + path.display() + ))); + } + } + Ok(InspectedBinary { + format: format.into(), + libraries, + imported_symbols: symbols.len(), + }) +} + +fn run_smoke( + root: &Path, + binary: &Path, + arguments: &[String], + required_output: bool, + trap: &HelperTrap, +) -> Result { + let output = unique_scratch("metacrate-runtime-evidence")?; + let resolved = arguments + .iter() + .map(|argument| { + if argument == "{output}" { + output.to_string_lossy().into_owned() + } else { + argument.clone() + } + }) + .collect::>(); + let existing_path = std::env::var_os("PATH").unwrap_or_default(); + let joined = std::env::join_paths( + std::iter::once(trap.directory.as_os_str()).chain( + std::env::split_paths(&existing_path) + .map(PathBuf::into_os_string) + .collect::>() + .iter() + .map(std::ffi::OsString::as_os_str), + ), + ) + .map_err(|error| MatrixError::new(format!("construct helper-trap PATH: {error}")))?; + let mut command = Command::new(binary); + command + .args(&resolved) + .current_dir(root) + .env("PATH", joined) + .env("HTTP_PROXY", "http://127.0.0.1:9") + .env("HTTPS_PROXY", "http://127.0.0.1:9") + .env("ALL_PROXY", "http://127.0.0.1:9") + .env("NO_PROXY", "127.0.0.1,localhost") + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()); + for name in CREDENTIAL_ENV { + command.env_remove(name); + } + let mut child = command.spawn().map_err(|error| { + MatrixError::new(format!("start runtime smoke {}: {error}", binary.display())) + })?; + let started = Instant::now(); + let status = loop { + if let Some(status) = child.try_wait()? { + break status; + } + if started.elapsed() >= SMOKE_TIMEOUT { + child.kill()?; + let _ = child.wait(); + return Err(MatrixError::new(format!( + "runtime smoke timed out: {} {}", + binary.display(), + resolved.join(" ") + ))); + } + thread::sleep(Duration::from_millis(10)); + }; + if !status.success() { + return Err(MatrixError::new(format!( + "runtime smoke failed with {status}: {} {}", + binary.display(), + resolved.join(" ") + ))); + } + let output_created = output.is_file(); + if required_output && !output_created { + return Err(MatrixError::new(format!( + "runtime smoke did not create required evidence: {} {}", + binary.display(), + resolved.join(" ") + ))); + } + if output_created { + fs::remove_file(&output)?; + } + Ok(SmokeEvidence { + arguments: arguments.to_vec(), + exit_code: status.code().unwrap_or(0), + output_created, + }) +} + +struct HelperTrap { + directory: PathBuf, + marker: PathBuf, +} + +impl Drop for HelperTrap { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.directory); + } +} + +impl HelperTrap { + fn new() -> Result { + let directory = unique_scratch("metacrate-helper-trap")?; + fs::create_dir(&directory)?; + let marker = directory.join("invoked.txt"); + for name in [ + "dotnet", + "mono", + "csc", + "mcs", + "pwsh", + "powershell", + "curl", + "wget", + ] { + write_trap(&directory, &marker, name)?; + } + Ok(Self { directory, marker }) + } + + fn verify_clean(&self) -> Result<()> { + if self.marker.exists() { + return Err(MatrixError::new(format!( + "runtime smoke invoked a forbidden helper: {}", + fs::read_to_string(&self.marker).unwrap_or_default().trim() + ))); + } + Ok(()) + } +} + +struct ScratchDir { + path: PathBuf, +} + +impl ScratchDir { + fn new(prefix: &str) -> Result { + let path = unique_scratch(prefix)?; + fs::create_dir(&path)?; + Ok(Self { path }) + } +} + +impl Drop for ScratchDir { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.path); + } +} + +#[cfg(unix)] +fn write_trap(directory: &Path, marker: &Path, name: &str) -> Result<()> { + use std::os::unix::fs::PermissionsExt as _; + let path = directory.join(name); + fs::write( + &path, + format!( + "#!/bin/sh\nprintf '%s\\n' '{name}' > '{}'\nexit 97\n", + marker.display() + ), + )?; + fs::set_permissions(&path, fs::Permissions::from_mode(0o700))?; + Ok(()) +} + +#[cfg(windows)] +fn write_trap(directory: &Path, marker: &Path, name: &str) -> Result<()> { + fs::write( + directory.join(format!("{name}.cmd")), + format!("@echo {name}>\"{}\"\r\n@exit /b 97\r\n", marker.display()), + )?; + Ok(()) +} + +fn unique_scratch(prefix: &str) -> Result { + let nanos = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|error| MatrixError::new(format!("system clock error: {error}")))? + .as_nanos(); + let path = std::env::temp_dir().join(format!("{prefix}-{}-{nanos}", std::process::id())); + if path.exists() { + return Err(MatrixError::new(format!( + "scratch path already exists: {}", + path.display() + ))); + } + Ok(path) +} + +fn command_output(root: &Path, program: &str, arguments: &[&str]) -> Result { + let output = Command::new(program) + .args(arguments) + .current_dir(root) + .output()?; + if !output.status.success() { + return Err(MatrixError::new(format!( + "{program} {} failed", + arguments.join(" ") + ))); + } + String::from_utf8(output.stdout) + .map(|value| value.trim().to_owned()) + .map_err(|_| MatrixError::new(format!("{program} output was not UTF-8"))) +} + +fn relative(root: &Path, path: &Path) -> Result { + path.strip_prefix(root) + .map_err(|_| MatrixError::new(format!("{} is outside the workspace", path.display())))? + .to_str() + .map(str::to_owned) + .ok_or_else(|| MatrixError::new(format!("{} is not UTF-8", path.display()))) +} + +fn hash_file(path: &Path) -> Result { + hash_reader(fs::File::open(path)?) +} + +fn hash_reader(mut reader: impl Read) -> Result { + let mut hash = Sha256::new(); + let mut buffer = vec![0_u8; 64 * 1024].into_boxed_slice(); + loop { + let count = reader.read(&mut buffer)?; + if count == 0 { + break; + } + hash.update(&buffer[..count]); + } + Ok(hex(&hash.finalize())) +} + +fn hash_bytes(bytes: &[u8]) -> String { + hex(&Sha256::digest(bytes)) +} + +fn hex(bytes: &[u8]) -> String { + bytes.iter().fold( + String::with_capacity(bytes.len() * 2), + |mut output, byte| { + write!(output, "{byte:02x}").expect("writing to a String cannot fail"); + output + }, + ) +} + +fn write_new_json(path: &Path, value: &impl Serialize) -> Result<()> { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + let mut file = OpenOptions::new().write(true).create_new(true).open(path)?; + let mut bytes = serde_json::to_vec_pretty(value)?; + bytes.push(b'\n'); + file.write_all(&bytes)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn policy_covers_every_shipped_binary_and_package() { + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../.."); + let policy: Policy = + serde_json::from_slice(&fs::read(root.join(POLICY_PATH)).unwrap()).unwrap(); + validate_policy(&policy).unwrap(); + assert_eq!(policy.release_packages.len(), 16); + assert_eq!(policy.binaries.len(), 10); + } + + #[test] + fn unsafe_and_managed_package_paths_are_rejected() { + assert!(validate_archive_path(Path::new("crate/src/lib.rs")).is_ok()); + assert!(validate_archive_path(Path::new("../escape")).is_err()); + assert!(reject_managed_extension(Path::new("crate/payload.dll")).is_err()); + assert!(reject_managed_extension(Path::new("crate/src/lib.rs")).is_ok()); + } + + #[test] + fn packaged_dependency_paths_are_rejected_but_target_paths_are_allowed() { + let invalid = b"[dependencies.example]\nversion = \"1\"\npath = \"../example\"\n"; + assert!(reject_packaged_path_dependencies(invalid, Path::new("Cargo.toml")).is_err()); + let inline = b"[dependencies]\nexample = { version = \"1\", path = \"../example\" }\n"; + assert!(reject_packaged_path_dependencies(inline, Path::new("Cargo.toml")).is_err()); + + let valid = b"[[bin]]\nname = \"example\"\npath = \"src/main.rs\"\n"; + assert!(reject_packaged_path_dependencies(valid, Path::new("Cargo.toml")).is_ok()); + } +} diff --git a/tools/ci-matrix/src/documentation.rs b/tools/ci-matrix/src/documentation.rs index c9ace3a..30a5ade 100644 --- a/tools/ci-matrix/src/documentation.rs +++ b/tools/ci-matrix/src/documentation.rs @@ -8,6 +8,8 @@ use std::path::{Path, PathBuf}; const MAPPING_PATH: &str = "api/RUST-MAPPING.tsv"; const TYPES_PATH: &str = "api/RUST-TYPES.tsv"; const GUIDE_PATH: &str = "docs/rust-api-guide.md"; +const CORE_GUIDE_MIRROR: &str = "crates/libremetaverse/README.md"; +const REPOSITORY_CONTENT_ROOT: &str = "https://git.rfc1437.de/hugo/MetaCrate/src/branch/main/"; const REPORT_PATH: &str = "api/DOCUMENTATION-COVERAGE.md"; const UPSTREAM_COMMIT: &str = "2aa70bb68513b39795da5d13c88f31b86e85a3ba"; @@ -234,14 +236,46 @@ fn validate_crates(root: &Path) -> Result<()> { } } let core = fs::read_to_string(root.join("crates/libremetaverse/src/lib.rs"))?; - if !core.contains("include_str!(\"../../../docs/rust-api-guide.md\")") { + if !core.contains("include_str!(\"../README.md\")") { return Err(MatrixError::new( "the tested Rust API guide is not included in libremetaverse rustdoc", )); } + let guide = fs::read_to_string(root.join(GUIDE_PATH))?; + let packaged_guide = fs::read_to_string(root.join(CORE_GUIDE_MIRROR))?; + if package_guide_links(&guide) != packaged_guide { + return Err(MatrixError::new( + "the packaged libremetaverse rustdoc guide is not the deterministic repository-link form of the tested guide", + )); + } Ok(()) } +fn package_guide_links(guide: &str) -> String { + guide + .replace("(../api/", &format!("({REPOSITORY_CONTENT_ROOT}api/")) + .replace( + "(release-ci-matrix.md", + &format!("({REPOSITORY_CONTENT_ROOT}docs/release-ci-matrix.md"), + ) + .replace( + "(../crates/libremetaverse/examples/", + &format!("({REPOSITORY_CONTENT_ROOT}crates/libremetaverse/examples/"), + ) + .replace( + "(concurrency-hardening.md", + &format!("({REPOSITORY_CONTENT_ROOT}docs/concurrency-hardening.md"), + ) + .replace( + "(live-grid-smoke.md", + &format!("({REPOSITORY_CONTENT_ROOT}docs/live-grid-smoke.md"), + ) + .replace( + "(../programs/README.md", + &format!("({REPOSITORY_CONTENT_ROOT}programs/README.md"), + ) +} + fn validate_guide(guide: &str) -> Result<()> { if !guide.contains("https://github.com/cinderblocks/libremetaverse") || !guide.contains(UPSTREAM_COMMIT) diff --git a/tools/ci-matrix/src/lib.rs b/tools/ci-matrix/src/lib.rs index f43669b..43d78fe 100644 --- a/tools/ci-matrix/src/lib.rs +++ b/tools/ci-matrix/src/lib.rs @@ -12,11 +12,13 @@ use std::process::{Command, ExitStatus, Stdio}; use std::time::{SystemTime, UNIX_EPOCH}; mod api_surface; +mod artifact; mod dependency; mod documentation; mod provenance; pub use api_surface::{audit_api_surface, write_api_baseline}; +pub use artifact::audit_artifacts; pub use dependency::audit_dependencies; pub use documentation::{audit_documentation, write_documentation_report}; pub use provenance::{audit_provenance, write_provenance_reports}; diff --git a/tools/ci-matrix/src/main.rs b/tools/ci-matrix/src/main.rs index 7b7f2d6..f8288cb 100644 --- a/tools/ci-matrix/src/main.rs +++ b/tools/ci-matrix/src/main.rs @@ -1,6 +1,7 @@ use metacrate_ci_matrix::{ - audit, audit_api_surface, audit_dependencies, audit_documentation, audit_provenance, load, run, - workspace_root, write_api_baseline, write_documentation_report, write_provenance_reports, + audit, audit_api_surface, audit_artifacts, audit_dependencies, audit_documentation, + audit_provenance, load, run, workspace_root, write_api_baseline, write_documentation_report, + write_provenance_reports, }; use std::path::{Path, PathBuf}; @@ -49,6 +50,9 @@ fn execute() -> Result<(), Box> { audit_dependencies(&root, &evidence)?; println!("dependency policy: ok ({})", evidence.display()); } + Some("artifact-audit") => { + artifact_audit_command(&root, arguments)?; + } Some("documentation-report") if arguments.next().is_none() => { write_documentation_report(&root)?; println!("documentation coverage report: updated"); @@ -94,7 +98,7 @@ fn execute() -> Result<(), Box> { } _ => { return Err( - "usage: ci-matrix audit | run PROFILE --evidence FILE | dependency-audit --evidence FILE | documentation-report | documentation-audit --evidence FILE | api-baseline-write | api-audit --evidence FILE | provenance-report | provenance-audit --evidence FILE" + "usage: ci-matrix audit | run PROFILE --evidence FILE | dependency-audit --evidence FILE | artifact-audit --artifact-dir DIR --package-dir DIR --evidence FILE | documentation-report | documentation-audit --evidence FILE | api-baseline-write | api-audit --evidence FILE | provenance-report | provenance-audit --evidence FILE" .into(), ); } @@ -102,6 +106,42 @@ fn execute() -> Result<(), Box> { Ok(()) } +fn artifact_audit_command( + root: &Path, + mut arguments: impl Iterator, +) -> Result<(), Box> { + let artifact_dir = option(&mut arguments, "--artifact-dir")?; + let package_dir = option(&mut arguments, "--package-dir")?; + let evidence = option(&mut arguments, "--evidence")?; + if arguments.next().is_some() { + return Err( + "usage: ci-matrix artifact-audit --artifact-dir DIR --package-dir DIR --evidence FILE" + .into(), + ); + } + let artifact_dir = absolute_or_rooted(root, &artifact_dir); + let package_dir = absolute_or_rooted(root, &package_dir); + let evidence = absolute_or_rooted(root, &evidence); + audit_artifacts(root, &artifact_dir, &package_dir, &evidence)?; + println!("native release artifacts: ok ({})", evidence.display()); + Ok(()) +} + +fn option( + arguments: &mut impl Iterator, + expected: &str, +) -> Result> { + let flag = arguments + .next() + .ok_or_else(|| format!("missing {expected}"))?; + if flag != expected { + return Err(format!("expected {expected}, found {flag}").into()); + } + arguments + .next() + .ok_or_else(|| format!("missing value for {expected}").into()) +} + fn provenance_audit_command( root: &Path, mut arguments: impl Iterator, diff --git a/tools/ci-matrix/src/provenance.rs b/tools/ci-matrix/src/provenance.rs index a878718..ea079ce 100644 --- a/tools/ci-matrix/src/provenance.rs +++ b/tools/ci-matrix/src/provenance.rs @@ -22,11 +22,12 @@ const GENERATED_RELEASE_PATHS: [&str; 4] = [ NATIVE_NOTICE_PATH, DISTRIBUTION_MANIFEST_PATH, ]; -const MATERIAL_ROOTS: [&str; 4] = [ +const MATERIAL_ROOTS: [&str; 5] = [ "codegen/inputs", "tests/fixtures", "fuzz/corpus", "benchmarks/fixtures", + "crates/libremetaverse/assets", ]; const BUNDLED_EXTENSIONS: [&str; 18] = [ "a", "animatn", "bmp", "bodypart", "clothing", "dll", "dylib", "gesture", "gif", "jpeg", "jpg",