diff --git a/.github/workflows/build-aot.yml b/.github/workflows/build-aot.yml
new file mode 100644
index 0000000..b58f2ec
--- /dev/null
+++ b/.github/workflows/build-aot.yml
@@ -0,0 +1,145 @@
+name: Build AOT
+
+# Traces a snapshot, translates its hot blocks, and emits the engine modules
+# every other workflow consumes. See docs/design/release-strategy.md.
+#
+# The output is wasm32-wasip2 and therefore host-independent: one build serves
+# all five platform targets. Running this inside build-binaries' matrix would
+# pay the whole pipeline five times.
+
+on:
+ workflow_call:
+ inputs:
+ snapshot:
+ description: 'Snapshot id to trace against'
+ required: false
+ default: 'alpine-3.23.0-256mb'
+ type: string
+ workflow_dispatch:
+ inputs:
+ snapshot:
+ description: 'Snapshot id to trace against'
+ required: false
+ default: 'alpine-3.23.0-256mb'
+ type: string
+
+jobs:
+ build-aot:
+ name: Trace and translate
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Resolve snapshot in registry
+ id: registry
+ env:
+ SNAPSHOT: ${{ inputs.snapshot }}
+ run: |
+ python3 - <<'PY' >> "$GITHUB_OUTPUT"
+ import json, os, urllib.request
+
+ snap_id = os.environ["SNAPSHOT"]
+ registry = json.load(urllib.request.urlopen(
+ "https://registry.vpod.sh/v1/snapshots.json", timeout=60))
+ entry = next((s for s in registry["snapshots"] if s["id"] == snap_id), None)
+ if entry is None:
+ raise SystemExit(f"snapshot '{snap_id}' not in registry")
+ print(f"sha256={entry['sha256']}")
+ print(f"url={entry['url']}")
+ PY
+
+ - name: Cache AOT modules
+ id: cache
+ uses: actions/cache@v4
+ with:
+ path: dist/aot
+ key: >-
+ aot-${{ inputs.snapshot }}-${{ steps.registry.outputs.sha256 }}-${{ hashFiles(
+ 'crates/riscv-core/**',
+ 'crates/machine/**',
+ 'crates/vpod-translate/**',
+ 'crates/wasi-component/**',
+ 'scripts/aot-snapshot.sh',
+ 'scripts/build-wasm.sh') }}
+
+ - name: Install Rust
+ if: steps.cache.outputs.cache-hit != 'true'
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: wasm32-wasip2
+
+ - name: Cache cargo
+ if: steps.cache.outputs.cache-hit != 'true'
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ key: aot-cargo-${{ hashFiles('**/Cargo.lock') }}
+
+ - name: Fetch snapshot
+ if: steps.cache.outputs.cache-hit != 'true'
+ env:
+ SNAPSHOT: ${{ inputs.snapshot }}
+ SNAPSHOT_URL: ${{ steps.registry.outputs.url }}
+ SNAPSHOT_SHA256: ${{ steps.registry.outputs.sha256 }}
+ run: |
+ python3 -m pip install --quiet lz4
+ python3 - <<'PY'
+ import hashlib, os, shutil, urllib.request
+ from pathlib import Path
+ import lz4.frame
+
+ snap_id = os.environ["SNAPSHOT"]
+ url = os.environ["SNAPSHOT_URL"]
+ sha256 = os.environ["SNAPSHOT_SHA256"]
+
+ dist = Path("dist"); dist.mkdir(exist_ok=True)
+ packed = dist / f"{snap_id}.snap.lz4"
+ with urllib.request.urlopen(url, timeout=300) as r, open(packed, "wb") as f:
+ shutil.copyfileobj(r, f)
+
+ digest = hashlib.sha256(packed.read_bytes()).hexdigest()
+ if digest != sha256:
+ raise SystemExit(f"checksum mismatch: {digest} != {sha256}")
+
+ out = dist / f"{snap_id}.snap"
+ with lz4.frame.open(str(packed), "rb") as src, open(out, "wb") as dst:
+ shutil.copyfileobj(src, dst)
+ packed.unlink()
+ print(f"{out} ({out.stat().st_size/1e6:.1f} MB)")
+ PY
+
+ - name: Trace and translate
+ if: steps.cache.outputs.cache-hit != 'true'
+ run: ./scripts/aot-snapshot.sh "dist/${{ inputs.snapshot }}.snap" --force
+
+ - name: Build engine modules
+ if: steps.cache.outputs.cache-hit != 'true'
+ run: ./scripts/build-wasm.sh
+
+ - name: Collect
+ if: steps.cache.outputs.cache-hit != 'true'
+ run: |
+ mkdir -p dist/aot
+ cp sdks/python/vpod/vpod_wasi_lib_aot.wasm dist/aot/
+ cp sdks/python/vpod/vpod_wasi_lib.wasm dist/aot/
+ cp target/wasm32-wasip2/release/vpod-wasi-cli.wasm dist/aot/
+ printf '%s %s\n' "${{ inputs.snapshot }}" "${{ steps.registry.outputs.sha256 }}" > dist/aot/TRACED_SNAPSHOT
+ ls -lh dist/aot
+
+ - name: Verify
+ run: |
+ for f in vpod_wasi_lib_aot.wasm vpod_wasi_lib.wasm vpod-wasi-cli.wasm; do
+ test -s "dist/aot/$f" || { echo "missing dist/aot/$f" >&2; exit 1; }
+ done
+
+ - name: Upload
+ uses: actions/upload-artifact@v4
+ with:
+ name: aot-wasm
+ path: dist/aot
+ retention-days: 1
+ overwrite: true
diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml
index 4d2eaca..19cf23c 100644
--- a/.github/workflows/build-binaries.yml
+++ b/.github/workflows/build-binaries.yml
@@ -1,21 +1,41 @@
name: Build Binaries
on:
- release:
- types: [published]
+ workflow_call:
+ inputs:
+ tag:
+ description: 'Release tag to upload binaries to (e.g. v0.1.3)'
+ required: true
+ type: string
+ snapshot:
+ description: 'Snapshot the AOT module is traced against'
+ required: false
+ default: 'alpine-3.23.0-256mb'
+ type: string
workflow_dispatch:
inputs:
tag:
description: 'Release tag to upload binaries to (e.g. v0.1.3)'
+ required: true
+ type: string
+ snapshot:
+ description: 'Snapshot the AOT module is traced against'
required: false
+ default: 'alpine-3.23.0-256mb'
type: string
permissions:
contents: write
jobs:
+ build-aot:
+ uses: ./.github/workflows/build-aot.yml
+ with:
+ snapshot: ${{ inputs.snapshot }}
+
build:
name: ${{ matrix.target }}
+ needs: build-aot
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
@@ -44,14 +64,14 @@ jobs:
- name: Inject version
shell: bash
run: |
- V="${{ github.event_name == 'release' && github.ref_name || inputs.tag }}"
+ V="${{ inputs.tag }}"
V="${V#v}"
perl -i -pe "s/^version = .*/version = \"$V\"/" crates/vpod/Cargo.toml
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
- targets: wasm32-wasip2,${{ matrix.target }}
+ targets: ${{ matrix.target }}
- name: Install cargo-zigbuild (Linux aarch64)
if: matrix.zigbuild
@@ -60,8 +80,15 @@ jobs:
cargo install cargo-zigbuild
rustup target add aarch64-unknown-linux-gnu
- - name: Build WASM component
- run: cargo build --release --target wasm32-wasip2 -p wasi-component
+ - name: Fetch AOT engine module
+ uses: actions/download-artifact@v4
+ with:
+ name: aot-wasm
+ path: dist/aot
+
+ - name: Place module for build.rs
+ shell: bash
+ run: cp dist/aot/vpod-wasi-cli.wasm crates/vpod/vpod-wasi-cli.wasm
- name: Build binary (native)
if: "!matrix.zigbuild"
@@ -92,7 +119,7 @@ jobs:
- name: Upload to release
uses: softprops/action-gh-release@v2
with:
- tag_name: ${{ github.event_name == 'release' && github.ref_name || inputs.tag }}
+ tag_name: ${{ inputs.tag }}
files: ${{ env.ASSET }}
- name: Upload artifact
diff --git a/.github/workflows/build-snapshots.yml b/.github/workflows/build-snapshots.yml
new file mode 100644
index 0000000..d971f37
--- /dev/null
+++ b/.github/workflows/build-snapshots.yml
@@ -0,0 +1,180 @@
+name: Build Snapshots
+
+# Builds the registry snapshots and (optionally) publishes them to R2.
+# See docs/design/release-strategy.md — this is "step 0": any snapshot change
+# must be in the registry BEFORE build-aot traces against it, or the release
+# ships an engine whose page hashes never match (silent interpreter fallback).
+#
+# Deliberately NOT run on every release: snapshot builds are not reproducible
+# (apk pulls whatever Alpine serves that day), so every rebuild changes the
+# bytes — and republishing changed bytes under the same ids silently degrades
+# AOT for every previously shipped engine. Publish only when guest content
+# actually changed, and always cut a release right after.
+#
+# The registry stores lz4-compressed files under the .snap name; sha256 and
+# size in snapshots.json are of the *compressed* bytes (what the SDK and
+# build-aot.yml verify before decompressing).
+
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'Registry manifest version (e.g. 0.5.0)'
+ required: true
+ type: string
+ publish:
+ description: 'Upload to R2 (off = dry run, artifacts only)'
+ required: false
+ default: false
+ type: boolean
+ workflow_call:
+ inputs:
+ version:
+ required: true
+ type: string
+ publish:
+ required: false
+ default: false
+ type: boolean
+ secrets:
+ R2_ACCOUNT_ID:
+ required: false
+ R2_ACCESS_KEY_ID:
+ required: false
+ R2_SECRET_ACCESS_KEY:
+ required: false
+ R2_BUCKET:
+ required: false
+
+jobs:
+ build-snapshots:
+ name: Build and publish snapshots
+ runs-on: ubuntu-latest
+ timeout-minutes: 300
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install host tools
+ run: |
+ sudo apt-get update -q
+ sudo apt-get install -y -q libarchive-tools cpio lz4
+
+ - name: Install Zig
+ uses: mlugg/setup-zig@v1
+
+ - name: Install Rust
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Cache cargo
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ key: snapshots-cargo-${{ hashFiles('**/Cargo.lock') }}
+
+ - name: Build default snapshot (alpine-3.23.0-256mb)
+ run: ./scripts/build-default-snapshot.sh
+
+ - name: Build 512MB base snapshot (vsnap-base-512mb)
+ run: ./scripts/build-default-snapshot.sh --ram 512
+
+ - name: Build data snapshot (vsnap-data-512mb)
+ run: ./scripts/build-data-snapshot.sh
+
+ - name: Compress for the registry
+ run: |
+ mkdir -p dist/registry
+ lz4 -9 -f dist/alpine-3.23.0-256mb.snap dist/registry/alpine-3.23.0-256mb.snap
+ cp dist/registry/alpine-3.23.0-256mb.snap dist/registry/vsnap-base-256mb.snap
+ lz4 -9 -f dist/alpine-3.23.0-512mb.snap dist/registry/vsnap-base-512mb.snap
+ lz4 -9 -f dist/vsnap-data-512mb.snap dist/registry/vsnap-data-512mb.snap
+ ls -lh dist/registry
+
+ - name: Generate snapshots.json
+ env:
+ VERSION: ${{ inputs.version }}
+ run: |
+ python3 - <<'PY'
+ import hashlib, json, os, urllib.request
+ from pathlib import Path
+
+ registry_dir = Path("dist/registry")
+ manifest = json.load(urllib.request.urlopen(
+ "https://registry.vpod.sh/v1/snapshots.json", timeout=60))
+
+ manifest["version"] = os.environ["VERSION"]
+
+ # A snapshot id we built but the live manifest doesn't know yet is a
+ # new variant: take its metadata from the repo template, pointing the
+ # url at the production path.
+ template = json.loads(
+ Path("docs/v0.4.1-registry/test/snapshots.json").read_text())
+ known_ids = {entry["id"] for entry in manifest["snapshots"]}
+ for entry in template["snapshots"]:
+ if entry["id"] in known_ids:
+ continue
+ if not (registry_dir / f"{entry['id']}.snap").exists():
+ continue
+ entry["url"] = f"https://registry.vpod.sh/v1/{entry['id']}.snap"
+ manifest["snapshots"].append(entry)
+ print(f" {entry['id']}: new snapshot, added from template")
+
+ for entry in manifest["snapshots"]:
+ built = registry_dir / f"{entry['id']}.snap"
+ if not built.exists():
+ print(f" {entry['id']}: not rebuilt, keeping registry entry")
+ continue
+ data = built.read_bytes()
+ entry["sha256"] = hashlib.sha256(data).hexdigest()
+ entry["size"] = len(data)
+ print(f" {entry['id']}: sha256={entry['sha256'][:12]}… size={entry['size']}")
+
+ (registry_dir / "snapshots.json").write_text(
+ json.dumps(manifest, indent=2) + "\n")
+ PY
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: registry-snapshots
+ path: dist/registry/snapshots.json
+ retention-days: 30
+
+ - name: Publish to R2
+ if: ${{ inputs.publish }}
+ env:
+ AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+ AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+ AWS_DEFAULT_REGION: auto
+ ENDPOINT: https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com
+ BUCKET: ${{ secrets.R2_BUCKET }}
+ run: |
+ for f in dist/registry/*.snap; do
+ aws s3 cp "$f" "s3://$BUCKET/v1/$(basename "$f")" --endpoint-url "$ENDPOINT"
+ done
+ aws s3 cp dist/registry/snapshots.json "s3://$BUCKET/v1/snapshots.json" \
+ --endpoint-url "$ENDPOINT" --content-type application/json
+
+ - name: Verify the live registry
+ if: ${{ inputs.publish }}
+ run: |
+ python3 - <<'PY'
+ import hashlib, json, urllib.request
+ from pathlib import Path
+
+ local = json.loads(Path("dist/registry/snapshots.json").read_text())
+ live = json.load(urllib.request.urlopen(
+ "https://registry.vpod.sh/v1/snapshots.json", timeout=60))
+ if live != local:
+ raise SystemExit("live snapshots.json does not match what we uploaded")
+
+ for entry in local["snapshots"]:
+ with urllib.request.urlopen(entry["url"], timeout=300) as r:
+ digest = hashlib.sha256(r.read()).hexdigest()
+ if digest != entry["sha256"]:
+ raise SystemExit(f"{entry['id']}: served bytes do not match manifest")
+ print(f"{entry['id']}: ok")
+ PY
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 13e8f08..d8972b6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -26,6 +26,9 @@ jobs:
- name: Check formatting
run: cargo fmt --all -- --check
+ - name: Generate AOT stub
+ run: ./scripts/aot-stub.sh
+
- name: Build WASM component
run: cargo build --release --target wasm32-wasip2 -p wasi-component
@@ -45,6 +48,9 @@ jobs:
with:
targets: wasm32-wasip2
+ - name: Generate AOT stub
+ run: ./scripts/aot-stub.sh
+
- name: Build WASM component
run: cargo build --release --target wasm32-wasip2 -p wasi-component
@@ -65,6 +71,11 @@ jobs:
- name: Install bsdtar
run: sudo apt-get update && sudo apt-get install -y libarchive-tools
+ - name: Install Zig
+ uses: mlugg/setup-zig@v2
+ with:
+ version: 0.14.0
+
- name: Build Default Snapshot
run: ./scripts/build-default-snapshot.sh
@@ -81,6 +92,9 @@ jobs:
with:
targets: wasm32-wasip2
+ - name: Generate AOT stub
+ run: ./scripts/aot-stub.sh
+
- name: Build WASM library
run: cargo build --release --target wasm32-wasip2 -p wasi-component --lib
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 5e544eb..458310d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -7,58 +7,72 @@ on:
description: 'Version to publish (e.g. 0.2.0)'
required: true
type: string
- publish_crate:
- description: 'Publish vpod to crates.io'
+ snapshot:
+ description: 'Snapshot to trace the AOT module against'
required: false
- default: true
- type: boolean
+ default: 'alpine-3.23.0-256mb'
+ type: string
publish_python:
description: 'Publish Python SDK to PyPI'
required: false
default: true
type: boolean
+ build_snapshots:
+ description: 'Rebuild + publish registry snapshots first (only when guest content changed — republishing changes their sha256 and degrades AOT for older releases)'
+ required: false
+ default: false
+ type: boolean
+
+permissions:
+ contents: write
jobs:
- publish-crate:
- name: Publish – crates.io (vpod)
- if: ${{ inputs.publish_crate }}
- runs-on: ubuntu-latest
+ build-snapshots:
+ if: ${{ inputs.build_snapshots }}
+ uses: ./.github/workflows/build-snapshots.yml
+ with:
+ version: ${{ inputs.version }}
+ publish: true
+ secrets: inherit
+
+ build-aot:
+ needs: [build-snapshots]
+ if: ${{ !cancelled() && !failure() }}
+ uses: ./.github/workflows/build-aot.yml
+ with:
+ snapshot: ${{ inputs.snapshot }}
+
+ # Must exist before build-binaries can attach assets to it.
+ create-release:
+ name: Create draft release
+ runs-on: ubuntu-latest
steps:
- - name: Checkout repo
- uses: actions/checkout@v4
-
- - name: Inject version
- run: |
- V="${{ inputs.version }}"
- sed -i "s/^version = \".*\"/version = \"$V\"/" crates/vpod/Cargo.toml
-
- - name: Install Rust toolchain
- uses: dtolnay/rust-toolchain@stable
- with:
- targets: wasm32-wasip2
+ - uses: actions/checkout@v4
- - name: Build WASM component (required by vpod build.rs)
- run: |
- cargo build --release --target wasm32-wasip2 -p wasi-component
- cp target/wasm32-wasip2/release/vpod-wasi-cli.wasm crates/vpod/
-
- - name: Cache Cargo registry
- uses: actions/cache@v4
- with:
- path: |
- ~/.cargo/registry
- ~/.cargo/git
- key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
-
- - name: Publish to crates.io
+ - name: Create draft
env:
- CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- run: cargo publish -p vpod --allow-dirty
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ TAG: v${{ inputs.version }}
+ run: |
+ if gh release view "$TAG" >/dev/null 2>&1; then
+ echo "release $TAG already exists — reusing it"
+ exit 0
+ fi
+ gh release create "$TAG" --draft --generate-notes --title "$TAG"
+
+ build-binaries:
+ needs: [build-aot, create-release]
+ uses: ./.github/workflows/build-binaries.yml
+ with:
+ tag: v${{ inputs.version }}
+ snapshot: ${{ inputs.snapshot }}
publish-python:
name: Publish – PyPI (Python SDK)
- if: ${{ inputs.publish_python }}
+ # An explicit `if` replaces the implicit success() check, so guard it back.
+ if: ${{ !cancelled() && !failure() && inputs.publish_python }}
+ needs: build-aot
runs-on: ubuntu-latest
steps:
@@ -71,15 +85,17 @@ jobs:
sed -i "s/^version = \".*\"/version = \"$V\"/" sdks/python/pyproject.toml
sed -i "s/^__version__ = \".*\"/__version__ = \"$V\"/" sdks/python/vpod/__init__.py
- - name: Install Rust toolchain
- uses: dtolnay/rust-toolchain@stable
+ - name: Fetch AOT engine modules
+ uses: actions/download-artifact@v4
with:
- targets: wasm32-wasip2
+ name: aot-wasm
+ path: dist/aot
- - name: Build WASM component for Python SDK
+ - name: Place modules in the SDK
run: |
- cargo build --release --target wasm32-wasip2 -p wasi-component --lib
- cp target/wasm32-wasip2/release/vpod_wasi_lib.wasm sdks/python/vpod/
+ cp dist/aot/vpod_wasi_lib_aot.wasm sdks/python/vpod/
+ cp dist/aot/vpod_wasi_lib.wasm sdks/python/vpod/
+ ls -lh sdks/python/vpod/*.wasm
- name: Set up Python
uses: actions/setup-python@v5
@@ -93,9 +109,42 @@ jobs:
working-directory: sdks/python
run: python -m build
+ - name: Verify the wheel carries both modules
+ working-directory: sdks/python
+ run: |
+ WHEEL=$(ls dist/*.whl)
+ python3 -c "
+ import sys, zipfile
+ names = zipfile.ZipFile('$WHEEL').namelist()
+ for want in ('vpod/vpod_wasi_lib_aot.wasm', 'vpod/vpod_wasi_lib.wasm'):
+ if want not in names:
+ sys.exit(f'{want} missing from the wheel')
+ print(want, 'ok')
+ "
+
- name: Publish to PyPI
working-directory: sdks/python
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: twine upload dist/*
+
+ attach-provenance:
+ name: Attach AOT provenance
+ needs: [build-aot, create-release]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Fetch AOT artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: aot-wasm
+ path: dist/aot
+
+ - name: Upload provenance to draft
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GH_REPO: ${{ github.repository }}
+ TAG: v${{ inputs.version }}
+ run: |
+ cp dist/aot/TRACED_SNAPSHOT aot-provenance.txt
+ gh release upload "$TAG" aot-provenance.txt --clobber
diff --git a/.gitignore b/.gitignore
index 5baf059..8750298 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
# will have compiled files and executables
debug
target
+target-shadow
# These are backup files generated by rustfmt
**/*.rs.bk
@@ -29,3 +30,4 @@ __pycache__/
*.wasm
+crates/riscv-core/src/aot/generated.rs
diff --git a/Cargo.lock b/Cargo.lock
index 4f68d36..bb67b22 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -11,6 +11,41 @@ dependencies = [
"gimli",
]
+[[package]]
+name = "aead"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
+dependencies = [
+ "crypto-common",
+ "generic-array",
+]
+
+[[package]]
+name = "aes"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "aes-gcm"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
[[package]]
name = "aho-corasick"
version = "1.1.4"
@@ -129,6 +164,41 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+[[package]]
+name = "autocfg"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+
+[[package]]
+name = "aws-lc-rs"
+version = "1.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad"
+dependencies = [
+ "aws-lc-sys",
+ "zeroize",
+]
+
+[[package]]
+name = "aws-lc-sys"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444"
+dependencies = [
+ "cc",
+ "cmake",
+ "dunce",
+ "fs_extra",
+ "pkg-config",
+]
+
+[[package]]
+name = "base16ct"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
+
[[package]]
name = "base64"
version = "0.21.7"
@@ -141,6 +211,12 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+[[package]]
+name = "base64ct"
+version = "1.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
+
[[package]]
name = "bitflags"
version = "2.11.1"
@@ -279,6 +355,41 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+[[package]]
+name = "chacha20"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "chacha20poly1305"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
+dependencies = [
+ "aead",
+ "chacha20",
+ "cipher",
+ "poly1305",
+ "zeroize",
+]
+
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+ "zeroize",
+]
+
[[package]]
name = "clap"
version = "4.6.1"
@@ -319,6 +430,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
+[[package]]
+name = "cmake"
+version = "0.1.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
+dependencies = [
+ "cc",
+]
+
[[package]]
name = "cobs"
version = "0.3.0"
@@ -347,6 +467,12 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "const-oid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@@ -531,6 +657,18 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+[[package]]
+name = "crypto-bigint"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
+dependencies = [
+ "generic-array",
+ "rand_core 0.6.4",
+ "subtle",
+ "zeroize",
+]
+
[[package]]
name = "crypto-common"
version = "0.1.7"
@@ -541,6 +679,42 @@ dependencies = [
"typenum",
]
+[[package]]
+name = "ctr"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "4.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "curve25519-dalek-derive",
+ "digest",
+ "fiat-crypto",
+ "rustc_version",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "debugid"
version = "0.8.0"
@@ -550,6 +724,30 @@ dependencies = [
"uuid",
]
+[[package]]
+name = "der"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
+dependencies = [
+ "const-oid",
+ "der_derive",
+ "flagset",
+ "pem-rfc7468",
+ "zeroize",
+]
+
+[[package]]
+name = "der_derive"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "digest"
version = "0.10.7"
@@ -557,7 +755,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
+ "const-oid",
"crypto-common",
+ "subtle",
]
[[package]]
@@ -654,12 +854,77 @@ dependencies = [
"syn",
]
+[[package]]
+name = "dunce"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
+
+[[package]]
+name = "ecdsa"
+version = "0.16.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
+dependencies = [
+ "der",
+ "digest",
+ "elliptic-curve",
+ "rfc6979",
+ "signature",
+ "spki",
+]
+
+[[package]]
+name = "ed25519"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
+dependencies = [
+ "pkcs8",
+ "signature",
+]
+
+[[package]]
+name = "ed25519-dalek"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
+dependencies = [
+ "curve25519-dalek",
+ "ed25519",
+ "serde",
+ "sha2",
+ "subtle",
+ "zeroize",
+]
+
[[package]]
name = "either"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
+[[package]]
+name = "elliptic-curve"
+version = "0.13.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
+dependencies = [
+ "base16ct",
+ "crypto-bigint",
+ "digest",
+ "ff",
+ "generic-array",
+ "group",
+ "hkdf",
+ "pem-rfc7468",
+ "pkcs8",
+ "rand_core 0.6.4",
+ "sec1",
+ "subtle",
+ "zeroize",
+]
+
[[package]]
name = "embedded-io"
version = "0.4.0"
@@ -743,12 +1008,34 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "ff"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
+dependencies = [
+ "rand_core 0.6.4",
+ "subtle",
+]
+
+[[package]]
+name = "fiat-crypto"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
+
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+[[package]]
+name = "flagset"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe"
+
[[package]]
name = "foldhash"
version = "0.1.5"
@@ -781,6 +1068,12 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "fs_extra"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
+
[[package]]
name = "futures"
version = "0.3.32"
@@ -886,6 +1179,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
+ "zeroize",
]
[[package]]
@@ -915,6 +1209,16 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "ghash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
[[package]]
name = "gimli"
version = "0.31.1"
@@ -926,6 +1230,17 @@ dependencies = [
"stable_deref_trait",
]
+[[package]]
+name = "group"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
+dependencies = [
+ "ff",
+ "rand_core 0.6.4",
+ "subtle",
+]
+
[[package]]
name = "hashbrown"
version = "0.15.5"
@@ -957,6 +1272,24 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+[[package]]
+name = "hkdf"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
+dependencies = [
+ "hmac",
+]
+
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest",
+]
+
[[package]]
name = "http"
version = "1.4.1"
@@ -1029,7 +1362,7 @@ dependencies = [
"tokio",
"tokio-rustls",
"tower-service",
- "webpki-roots",
+ "webpki-roots 1.0.7",
]
[[package]]
@@ -1213,6 +1546,15 @@ dependencies = [
"web-time",
]
+[[package]]
+name = "inout"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
+dependencies = [
+ "generic-array",
+]
+
[[package]]
name = "io-extras"
version = "0.18.4"
@@ -1322,6 +1664,15 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+dependencies = [
+ "spin",
+]
+
[[package]]
name = "leb128"
version = "0.2.6"
@@ -1407,11 +1758,21 @@ dependencies = [
name = "machine"
version = "0.0.0"
dependencies = [
+ "const-oid",
+ "der",
"dirs 6.0.0",
+ "libc",
"log",
+ "p256",
+ "rand_core 0.6.4",
"riscv-core",
+ "rustls",
+ "rustls-pki-types",
+ "rustls-rustcrypto",
"serde",
"serde_json",
+ "webpki-roots 0.26.11",
+ "x509-cert",
]
[[package]]
@@ -1468,6 +1829,51 @@ dependencies = [
"riscv-core",
]
+[[package]]
+name = "num-bigint-dig"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
+dependencies = [
+ "lazy_static",
+ "libm",
+ "num-integer",
+ "num-iter",
+ "num-traits",
+ "rand 0.8.6",
+ "smallvec",
+ "zeroize",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+ "libm",
+]
+
[[package]]
name = "number_prefix"
version = "0.4.0"
@@ -1507,18 +1913,57 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+[[package]]
+name = "opaque-debug"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+[[package]]
+name = "p256"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
+dependencies = [
+ "ecdsa",
+ "elliptic-curve",
+ "primeorder",
+ "sha2",
+]
+
+[[package]]
+name = "p384"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
+dependencies = [
+ "ecdsa",
+ "elliptic-curve",
+ "primeorder",
+ "sha2",
+]
+
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+[[package]]
+name = "pem-rfc7468"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
+dependencies = [
+ "base64ct",
+]
+
[[package]]
name = "percent-encoding"
version = "2.3.2"
@@ -1531,12 +1976,67 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+[[package]]
+name = "pkcs1"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
+dependencies = [
+ "der",
+ "pkcs8",
+ "spki",
+]
+
+[[package]]
+name = "pkcs5"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6"
+dependencies = [
+ "der",
+ "spki",
+]
+
+[[package]]
+name = "pkcs8"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+dependencies = [
+ "der",
+ "pkcs5",
+ "spki",
+]
+
[[package]]
name = "pkg-config"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
+[[package]]
+name = "poly1305"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
+dependencies = [
+ "cpufeatures",
+ "opaque-debug",
+ "universal-hash",
+]
+
+[[package]]
+name = "polyval"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "opaque-debug",
+ "universal-hash",
+]
+
[[package]]
name = "portable-atomic"
version = "1.13.1"
@@ -1592,6 +2092,15 @@ dependencies = [
"syn",
]
+[[package]]
+name = "primeorder"
+version = "0.13.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
+dependencies = [
+ "elliptic-curve",
+]
+
[[package]]
name = "proc-macro2"
version = "1.0.106"
@@ -1875,7 +2384,17 @@ dependencies = [
"wasm-bindgen-futures",
"wasm-streams",
"web-sys",
- "webpki-roots",
+ "webpki-roots 1.0.7",
+]
+
+[[package]]
+name = "rfc6979"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
+dependencies = [
+ "hmac",
+ "subtle",
]
[[package]]
@@ -1898,6 +2417,28 @@ version = "0.0.0"
dependencies = [
"env_logger",
"log",
+ "rustc-hash",
+]
+
+[[package]]
+name = "rsa"
+version = "0.9.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
+dependencies = [
+ "const-oid",
+ "digest",
+ "num-bigint-dig",
+ "num-integer",
+ "num-traits",
+ "pkcs1",
+ "pkcs8",
+ "rand_core 0.6.4",
+ "sha2",
+ "signature",
+ "spki",
+ "subtle",
+ "zeroize",
]
[[package]]
@@ -1912,6 +2453,15 @@ version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
[[package]]
name = "rustix"
version = "0.38.44"
@@ -1954,10 +2504,11 @@ version = "0.23.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
dependencies = [
+ "aws-lc-rs",
"once_cell",
"ring",
"rustls-pki-types",
- "rustls-webpki",
+ "rustls-webpki 0.103.13",
"subtle",
"zeroize",
]
@@ -1972,12 +2523,54 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "rustls-rustcrypto"
+version = "0.0.2-alpha"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f12052947763ab8515f753315357599e9b0b4dab3b8ba15f30f725fe6d025557"
+dependencies = [
+ "aead",
+ "aes-gcm",
+ "chacha20poly1305",
+ "crypto-common",
+ "der",
+ "digest",
+ "ecdsa",
+ "ed25519-dalek",
+ "hmac",
+ "p256",
+ "p384",
+ "paste",
+ "pkcs8",
+ "rand_core 0.6.4",
+ "rsa",
+ "rustls",
+ "rustls-pki-types",
+ "rustls-webpki 0.102.8",
+ "sec1",
+ "sha2",
+ "signature",
+ "x25519-dalek",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.102.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
[[package]]
name = "rustls-webpki"
version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [
+ "aws-lc-rs",
"ring",
"rustls-pki-types",
"untrusted",
@@ -1995,6 +2588,20 @@ version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
+[[package]]
+name = "sec1"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
+dependencies = [
+ "base16ct",
+ "der",
+ "generic-array",
+ "pkcs8",
+ "subtle",
+ "zeroize",
+]
+
[[package]]
name = "semver"
version = "1.0.28"
@@ -2069,6 +2676,17 @@ dependencies = [
"serde",
]
+[[package]]
+name = "sha1"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
[[package]]
name = "sha2"
version = "0.10.9"
@@ -2095,6 +2713,16 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
+[[package]]
+name = "signature"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+dependencies = [
+ "digest",
+ "rand_core 0.6.4",
+]
+
[[package]]
name = "slab"
version = "0.4.12"
@@ -2120,6 +2748,22 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "spin"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
+
+[[package]]
+name = "spki"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
+dependencies = [
+ "base64ct",
+ "der",
+]
+
[[package]]
name = "sptr"
version = "0.3.2"
@@ -2271,6 +2915,27 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+[[package]]
+name = "tls_codec"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b"
+dependencies = [
+ "tls_codec_derive",
+ "zeroize",
+]
+
+[[package]]
+name = "tls_codec_derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "tokio"
version = "1.52.3"
@@ -2472,6 +3137,16 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+[[package]]
+name = "universal-hash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
+dependencies = [
+ "crypto-common",
+ "subtle",
+]
+
[[package]]
name = "untrusted"
version = "0.9.0"
@@ -2538,6 +3213,14 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "vpod-translate"
+version = "0.1.0"
+dependencies = [
+ "lz4_flex",
+ "riscv-core",
+]
+
[[package]]
name = "want"
version = "0.3.1"
@@ -3091,6 +3774,15 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "webpki-roots"
+version = "0.26.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
+dependencies = [
+ "webpki-roots 1.0.7",
+]
+
[[package]]
name = "webpki-roots"
version = "1.0.7"
@@ -3553,6 +4245,31 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
+[[package]]
+name = "x25519-dalek"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
+dependencies = [
+ "curve25519-dalek",
+ "rand_core 0.6.4",
+ "zeroize",
+]
+
+[[package]]
+name = "x509-cert"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94"
+dependencies = [
+ "const-oid",
+ "der",
+ "sha1",
+ "signature",
+ "spki",
+ "tls_codec",
+]
+
[[package]]
name = "yoke"
version = "0.8.2"
@@ -3622,6 +4339,20 @@ name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
[[package]]
name = "zerotrie"
diff --git a/Cargo.toml b/Cargo.toml
index a852c7d..30fc44d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,10 @@
[workspace]
-members = ["crates/riscv-core", "crates/machine", "crates/native-cli", "crates/wasi-component", "crates/vpod"]
+members = ["crates/riscv-core", "crates/machine", "crates/native-cli", "crates/wasi-component", "crates/vpod", "crates/vpod-translate"]
resolver = "3"
[workspace.package]
edition = "2024"
+
+[profile.release]
+lto = "fat"
+codegen-units = 1
diff --git a/README.md b/README.md
index a6c6fa9..56f8036 100644
--- a/README.md
+++ b/README.md
@@ -25,9 +25,13 @@ A `vpod` is a lightweight, portable sandbox that gives an untrusted process an i
## How it works
-A `vpod` runs a RISC‑V virtual machine compiled to WebAssembly, implementing the RV64GC specification. When you start a `vpod`, it boots from a snapshot, a saved VM state ready in under a second.
+A `vpod` runs a complete RISC‑V system (RV64GC, single vCPU) compiled to WebAssembly. Inside it boots a real Linux kernel with a real userspace, so shells, tools and daemons all behave like they would on actual hardware.
-The WASM component communicates with the host through WASI 0.2, providing controlled access to filesystem, networking, and standard I/O while keeping all execution state (CPU registers, memory, filesystem) isolated inside the sandbox.
+**Snapshots.** Instead of booting Linux from scratch, a `vpod` restores a snapshot: a saved machine state (CPU registers, RAM, filesystem) captured right after boot. Restoring one takes well under a second. Suspend works the same way in reverse, only dirty memory pages are written back to disk, so you can pause a sandbox and resume it later, even from another process.
+
+**Ahead-of-time translation.** Pure instruction-by-instruction emulation is slow, and WebAssembly rules out a runtime JIT. So at snapshot build time, the hottest guest code paths are translated from RISC‑V into native code that gets compiled into the WASM module itself. At runtime the emulator dispatches into these translated blocks when the guest code matches, and falls back to the interpreter when it doesn't. This is worth roughly 5x on CPU-bound work, with zero effect on isolation: translated code goes through the same MMU and memory checks as interpreted code.
+
+**The WASI boundary.** The WASM component talks to the host exclusively through WASI 0.2. The guest never sees host file descriptors, sockets, or memory: filesystem access goes through explicitly mounted directories, and networking goes through a user-mode network stack inside the component that only ever asks the host for plain outbound sockets. Everything else (guest kernel, processes, memory) lives inside the WASM linear memory and dies with it.
### RV64GC Specification
@@ -45,38 +49,6 @@ Reduces code size by 30%, improving instruction fetch speed and memory efficienc
## Getting started
-### CLI
-
-```bash
-curl -fsSL https://install.vpod.sh | sh
-```
-
->
-> Or install via PowerShell (windows)
->
-> ```bash
-> irm https://install.vpod.sh | iex
-> ```
->
->
-
->
-> Or install via cargo
->
-> ```bash
-> cargo install vpod
-> ```
->
->
-
-```bash
-# Pull a snapshot
-vpod pull alpine:latest
-
-# Start an interactive shell
-vpod
-```
-
### Python SDK
```bash
@@ -109,47 +81,112 @@ with Sandbox.create() as sandbox:
> [!IMPORTANT]
> The first call to `Sandbox.create()` downloads the default snapshot (`alpine`) and caches it locally if not already present.
-For more details, see the [full documentation](https://docs.vpod.sh/quickstart).
+> [!TIP]
+> The default snapshot ships with `apk` for system packages and `uv` for Python packages, so you can install what you need at runtime.
+
+
+### CLI
+
+```bash
+curl -fsSL https://install.vpod.sh | sh
+```
+
+>
+> Or install via PowerShell (windows)
+>
+> ```bash
+> irm https://install.vpod.sh | iex
+> ```
+>
+>
+
+```bash
+# Pull a snapshot
+vpod pull alpine:latest
+
+# Start an interactive shell
+vpod
+```
+
+## Documentation
+Visit [Vpod documentation](https://docs.vpod.sh/quickstart).
## Limitations
-- **Emulation overhead**: No hardware acceleration in the WASM component. CPU-intensive workloads may run slower than native.
-- **No GPU access**: CUDA, Metal, and hardware ML accelerators are not yet available. Support may be added in the future with wasi-nn.
-- **Env vars don't cross between shell and Python**: `sandbox.commands.run("export FOO=bar")` is not visible in `sandbox.code.run(...)`. Use the filesystem to share data between the two.
+- **Emulation overhead**: There is no hardware virtualization inside WebAssembly, so all guest code is emulated. The overhead depends entirely on the workload: I/O-bound and network-bound work runs close to native speed, while heavy CPU-bound work runs noticeably slower even with AOT translation. If your workload is mostly "run a tool, read a file, call an API", you won't notice.
+- **No GPU access**: CUDA, Metal, and hardware ML accelerators are not available. Support may be added in the future with wasi-nn.
## Contributing
-**Prerequisites**
-- Rust (latest stable)
-- Python 3.10+
+Contributions are welcome, from bug reports to new device support. Open an [issue](https://github.com/capsulerun/vpod/issues/new) to discuss anything substantial before building it.
+
+### Repository layout
+
+| Path | What it is |
+|:---|:---|
+| `crates/riscv-core` | RV64GC decoder and executor, MMU, and the AOT block runtime |
+| `crates/machine` | The machine model: RAM (copy-on-write), UART, PLIC/CLINT, virtio devices, snapshot save/restore |
+| `crates/wasi-component` | The WASM component (WASI 0.2) that wraps the machine for sandboxed use |
+| `crates/vpod` | The host CLI (`vpod`), which runs the WASM component |
+| `crates/native-cli` | A native (non-WASM) build of the emulator, used for development and debugging |
+| `crates/vpod-translate` | The AOT translator: turns traced hot RISC‑V code into Rust at snapshot build time |
+| `sdks/python` | The Python SDK (`pip install vpod`) |
+| `scripts/` | Build scripts for the WASM component, snapshots, and AOT translation |
+
+### Prerequisites
+
+- **Rust** (latest stable) with the `wasm32-wasip2` target: `rustup target add wasm32-wasip2`
+- **Python 3.10+** for the SDK
+- **Zig** (0.14) and **bsdtar**, only needed if you build snapshots yourself
+
+### Development setup
-**Development setup**
```bash
-# Build WASM component
+# One-time: generate the AOT stub (a fresh clone has no translated blocks)
+./scripts/aot-stub.sh
+
+# Build the WASM component (library + CLI)
./scripts/build-wasm.sh
-# Install CLI
+# Install the host CLI
cargo install --path crates/vpod
-# Install Python SDK in dev mode
-pip install -e sdks/python[dev]
-
-# Run tests
-cargo test # Rust tests
-pytest sdks/python/tests/ -v -m integration # Integration tests (requires WASM build)
+# Install the Python SDK in dev mode
+pip install -e "sdks/python[dev]"
```
-**Building snapshots**
+### Running tests
-The project uses pre-built Alpine snapshots from `registry.vpod.sh`. To build a custom snapshot:
+CI runs these on every PR, so run them before pushing:
```bash
-./scripts/build-default-snapshot.sh
+cargo fmt --all -- --check # formatting
+cargo clippy --all-targets --all-features -- -D warnings
+cargo test --all # Rust tests
+
+# Python SDK integration tests (needs the WASM library in place)
+cp target/wasm32-wasip2/release/vpod_wasi_lib.wasm sdks/python/vpod/
+pytest sdks/python/tests/ -v -m integration
```
-This creates `dist/alpine-3.23.0-256mb.snap`.
+### Building snapshots
+
+The project uses pre-built Alpine snapshots from `registry.vpod.sh`, so you normally don't need this. To build one locally:
+
+```bash
+./scripts/build-default-snapshot.sh # dist/alpine-3.23.0-256mb.snap
+./scripts/build-data-snapshot.sh # 512 MB variant with numpy/pandas/scipy
+```
> [!IMPORTANT]
-> To use it locally, uncomment lines in `resolve_snapshot()` in `crates/vpod/src/main.rs`.
+> To use a locally built snapshot, uncomment the lines in `resolve_snapshot()` in `crates/vpod/src/main.rs`.
+
+Snapshot builds can also run the AOT pass (`scripts/aot-snapshot.sh `), which traces a representative workload, translates the hot blocks, and rebuilds the emulator with them baked in. It takes a while; the stub from `aot-stub.sh` is fine for everyday development, everything works the same, just slower.
+
+### Pull requests
+
+- Keep PRs focused: one change per PR.
+- `fmt`, `clippy` and the test suite must pass (CI enforces all three).
+- If you touch the emulator's execution or memory paths, say how you validated correctness (the test suite at minimum; for subtle changes a boot plus a real workload in the guest is a good sanity check).
## License
diff --git a/crates/machine/Cargo.toml b/crates/machine/Cargo.toml
index 606b58b..a7a0efe 100644
--- a/crates/machine/Cargo.toml
+++ b/crates/machine/Cargo.toml
@@ -9,3 +9,18 @@ log = "0.4"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
dirs = "6"
+rustls = { version = "0.23", default-features = false, features = ["std", "tls12"] }
+rustls-rustcrypto = "0.0.2-alpha"
+rustls-pki-types = "1"
+webpki-roots = "0.26"
+x509-cert = { version = "0.2", features = ["builder"] }
+p256 = { version = "0.13", features = ["ecdsa", "pem", "pkcs8"] }
+der = { version = "0.7", features = ["pem"] }
+const-oid = { version = "0.9", features = ["db"] }
+rand_core = { version = "0.6", features = ["getrandom"] }
+
+[dev-dependencies]
+rustls = { version = "0.23", default-features = false, features = ["std", "tls12", "ring", "aws-lc-rs"] }
+
+[target.'cfg(unix)'.dev-dependencies]
+libc = "0.2"
diff --git a/crates/machine/assets/tls/vpod-ca-cert.pem b/crates/machine/assets/tls/vpod-ca-cert.pem
new file mode 100644
index 0000000..ec19dac
--- /dev/null
+++ b/crates/machine/assets/tls/vpod-ca-cert.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDDA12cG9kIGxv
+Y2FsIENBMB4XDTI2MDcxNTA3MjY1N1oXDTM2MDcxMjA3MjY1N1owGDEWMBQGA1UE
+AwwNdnBvZCBsb2NhbCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOYt0tJ8
+FrlckHPCsLPRqE5J0vYaKHdlV0AniqCz5OwLGgMaLZKGGTVJB42w5L1WqKKqp/tm
+Gwu0M49x3QxuFtajQjBAMB0GA1UdDgQWBBS2hEVRKfu/KzxnjhsIYN7H/is+5DAP
+BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAgNHADBE
+AiB3UGbX5SbemKzH2S/Sukz9hbPibxZpp3oMdvMgKYJc1QIgc5e3X7oymhHe5c51
+23V/+lL6Nzu4UcuirnZyZLCZHAY=
+-----END CERTIFICATE-----
diff --git a/crates/machine/assets/tls/vpod-ca-key.pem b/crates/machine/assets/tls/vpod-ca-key.pem
new file mode 100644
index 0000000..379a1a9
--- /dev/null
+++ b/crates/machine/assets/tls/vpod-ca-key.pem
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgZgXEZ+vmg2LpSUb2
+HFGLOhd+IAp5AVfMgo7gNe/1JYahRANCAATmLdLSfBa5XJBzwrCz0ahOSdL2Gih3
+ZVdAJ4qgs+TsCxoDGi2Shhk1SQeNsOS9Vqiiqqf7ZhsLtDOPcd0MbhbW
+-----END PRIVATE KEY-----
diff --git a/crates/machine/src/clint.rs b/crates/machine/src/clint.rs
index 1db1b46..4bf8cd9 100644
--- a/crates/machine/src/clint.rs
+++ b/crates/machine/src/clint.rs
@@ -39,6 +39,25 @@ impl Clint {
self.mtime += nanos / NANOS_PER_TICK;
}
+ pub fn nanos_until_timer(&self) -> Option {
+ const NANOS_PER_TICK: u64 = 1_000_000_000 / TIMER_FREQUENCY;
+
+ if self.mtimecmp == u64::MAX || self.mtimecmp <= self.mtime {
+ None
+ } else {
+ Some((self.mtimecmp - self.mtime) * NANOS_PER_TICK)
+ }
+ }
+
+ pub fn fast_forward_to_timer(&mut self) -> bool {
+ if self.mtimecmp != u64::MAX && self.mtimecmp > self.mtime {
+ self.mtime = self.mtimecmp;
+ true
+ } else {
+ false
+ }
+ }
+
pub fn mtime(&self) -> u64 {
self.mtime
}
diff --git a/crates/machine/src/cow_ram.rs b/crates/machine/src/cow_ram.rs
index 2183be1..85311db 100644
--- a/crates/machine/src/cow_ram.rs
+++ b/crates/machine/src/cow_ram.rs
@@ -1,13 +1,21 @@
// Copy-on-write guest RAM.
use std::sync::Arc;
+use std::sync::atomic::{AtomicU64, Ordering};
pub const PAGE_SIZE: usize = 4096;
+static EPOCH_SOURCE: AtomicU64 = AtomicU64::new(1);
+
+fn next_epoch() -> u64 {
+ EPOCH_SOURCE.fetch_add(1, Ordering::Relaxed)
+}
+
pub struct CowRam {
base: Arc>,
pages: Vec