diff --git a/.github/workflows/native-prebuilts.yml b/.github/workflows/native-prebuilts.yml deleted file mode 100644 index ef344187..00000000 --- a/.github/workflows/native-prebuilts.yml +++ /dev/null @@ -1,308 +0,0 @@ -name: Native and package validation - -on: - pull_request: - push: - tags: ['native-*'] - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: native-prebuilts-${{ github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -jobs: - verify: - name: Rust, Dart, and lifecycle tests - runs-on: ubuntu-22.04 - timeout-minutes: 60 - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - - name: Install Linux prerequisites - run: | - sudo apt-get update - sudo apt-get install -y build-essential libgtk-3-dev liblzma-dev ninja-build pkg-config perl - - name: Install pinned Rust toolchain - run: bash tool/ci/install_rust.sh --component clippy,rustfmt - - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 - with: - workspaces: rust - - uses: subosito/flutter-action@1508160852fb97248640997f7cfb38da241df0ba # v2.9.1 - with: - flutter-version: '3.47.2' - channel: stable - - name: Verify Rust - run: | - cargo fmt --manifest-path rust/Cargo.toml --check - cargo test --locked --all-targets --manifest-path rust/Cargo.toml - cargo clippy --locked --all-targets --manifest-path rust/Cargo.toml -- -D warnings - - name: Verify Dart and native lifecycle - run: | - flutter pub get - flutter analyze - flutter test - - name: Test example widgets - working-directory: example - run: flutter test - - name: Cache pinned cargo-audit binary - id: cargo-audit-cache - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 - with: - path: ~/.cargo/bin/cargo-audit - key: cargo-audit-${{ runner.os }}-${{ runner.arch }}-0.22.2-${{ hashFiles('rust/rust-toolchain.toml') }} - - name: Install pinned cargo-audit - if: steps.cargo-audit-cache.outputs.cache-hit != 'true' - run: cargo install cargo-audit --version 0.22.2 --locked - - name: Audit Rust dependencies - run: | - cargo audit --file rust/Cargo.lock \ - --deny unsound \ - --deny yanked \ - --ignore RUSTSEC-2021-0145 \ - --ignore RUSTSEC-2023-0071 - - windows-tests: - if: github.event_name == 'pull_request' - name: Windows time, IPv6, and file release tests - runs-on: windows-2022 - timeout-minutes: 60 - env: - NATIVE_TARGET: x86_64-pc-windows-msvc - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - - name: Use short Windows build paths - shell: pwsh - run: ./tool/ci/windows_paths.ps1 - - name: Install pinned Rust toolchain - run: bash tool/ci/install_rust.sh --target "$NATIVE_TARGET" - - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 - with: - workspaces: rust - key: windows-tests - cache-targets: false - - name: Test time limits, IPv6 fallback, and file release - run: bash tool/ci/test_windows.sh - - build: - if: github.event_name != 'pull_request' && github.event.deleted != true - name: ${{ matrix.target }} - runs-on: ${{ matrix.runner }} - timeout-minutes: 120 - strategy: - fail-fast: false - matrix: - include: - - {target: x86_64-unknown-linux-gnu, runner: ubuntu-22.04} - - {target: aarch64-unknown-linux-gnu, runner: ubuntu-24.04-arm} - - {target: x86_64-pc-windows-msvc, runner: windows-2022} - - {target: aarch64-apple-darwin, runner: macos-15} - - {target: x86_64-apple-darwin, runner: macos-15-intel} - - {target: aarch64-apple-ios, runner: macos-15} - - {target: aarch64-apple-ios-sim, runner: macos-15} - - {target: x86_64-apple-ios, runner: macos-15-intel} - - {target: armv7-linux-androideabi, runner: ubuntu-22.04} - - {target: aarch64-linux-android, runner: ubuntu-22.04} - - {target: x86_64-linux-android, runner: ubuntu-22.04} - env: - NATIVE_TARGET: ${{ matrix.target }} - CARGO_TARGET_DIR: ${{ github.workspace }}/rust/target - MACOSX_DEPLOYMENT_TARGET: '11.0' - IPHONEOS_DEPLOYMENT_TARGET: '13.0' - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - - name: Use short Windows build paths - if: runner.os == 'Windows' - shell: pwsh - run: ./tool/ci/windows_paths.ps1 - - name: Install Linux prerequisites - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install -y build-essential pkg-config perl - - name: Install Android NDK - if: contains(matrix.target, 'android') - run: | - "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" 'ndk;28.2.13676358' - echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/28.2.13676358" >> "$GITHUB_ENV" - - name: Install pinned Rust toolchain - run: bash tool/ci/install_rust.sh --target "$NATIVE_TARGET" - - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 - with: - workspaces: rust - key: ${{ matrix.target }} - # Windows output lives outside the workspace; cache its downloads only. - cache-targets: ${{ runner.os != 'Windows' }} - - name: Build and package - run: | - case "$NATIVE_TARGET" in - *android*) - ndk_bin="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin" - export PATH="$ndk_bin:$PATH" - key=${NATIVE_TARGET//-/_} - clang_target=${NATIVE_TARGET/armv7-/armv7a-} - export "CC_$key=$ndk_bin/${clang_target}24-clang" - export "CXX_$key=$ndk_bin/${clang_target}24-clang++" - export "AR_$key=$ndk_bin/llvm-ar" - export "RANLIB_$key=$ndk_bin/llvm-ranlib" - export "CARGO_TARGET_$(echo "$key" | tr '[:lower:]' '[:upper:]')_LINKER=$ndk_bin/${clang_target}24-clang" - export RUSTFLAGS='-C link-arg=-Wl,-z,max-page-size=16384' - ;; - *apple*) - export RUSTFLAGS='-C link-arg=-Wl,-install_name,@rpath/libtor_ffi_plugin.dylib' - case "$NATIVE_TARGET" in - aarch64-apple-ios) SDKROOT=$(xcrun --sdk iphoneos --show-sdk-path) ;; - *-ios*) SDKROOT=$(xcrun --sdk iphonesimulator --show-sdk-path) ;; - esac - export SDKROOT - ;; - esac - cargo build --locked --release --lib --manifest-path rust/Cargo.toml \ - --target "$NATIVE_TARGET" - case "$NATIVE_TARGET" in - *windows*) library=tor_ffi_plugin.dll ;; - *apple*) library=libtor_ffi_plugin.dylib ;; - *) library=libtor_ffi_plugin.so ;; - esac - mkdir -p artifacts - cp "$CARGO_TARGET_DIR/$NATIVE_TARGET/release/$library" "artifacts/$NATIVE_TARGET-$library" - - name: Test time limits, IPv6 fallback, and file release - if: runner.os == 'Windows' - run: bash tool/ci/test_windows.sh - - name: Smoke test desktop library - if: ${{ !contains(matrix.target, 'android') && !contains(matrix.target, '-ios') }} - run: python tool/ci/smoke_native.py artifacts/* - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: prebuilt-${{ matrix.target }} - path: artifacts/* - if-no-files-found: error - - package: - if: github.event_name == 'pull_request' - name: Package example (${{ matrix.platform }}) - runs-on: ${{ matrix.runner }} - timeout-minutes: 120 - strategy: - fail-fast: false - matrix: - include: - - {platform: linux, runner: ubuntu-22.04} - - {platform: android, runner: ubuntu-22.04} - - {platform: ios, runner: macos-15} - - {platform: macos, runner: macos-15} - - {platform: windows, runner: windows-2022} - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - - name: Install Linux prerequisites - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install -y build-essential clang cmake libgtk-3-dev liblzma-dev ninja-build pkg-config perl - - name: Install Android Java - if: matrix.platform == 'android' - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 - with: - distribution: temurin - java-version: '17' - - name: Install Android NDK - if: matrix.platform == 'android' - run: | - "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" 'ndk;28.2.13676358' - echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/28.2.13676358" >> "$GITHUB_ENV" - - name: Install pinned Rust toolchain - run: | - case "${{ matrix.platform }}" in - linux) target=x86_64-unknown-linux-gnu ;; - android) target=aarch64-linux-android ;; - ios) target=aarch64-apple-ios ;; - # flutter build macos is universal, so the hook also builds x86_64. - macos) target=aarch64-apple-darwin,x86_64-apple-darwin ;; - windows) target=x86_64-pc-windows-msvc ;; - esac - bash tool/ci/install_rust.sh --target "$target" - - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 - with: - workspaces: rust - key: package-${{ matrix.platform }} - - uses: subosito/flutter-action@1508160852fb97248640997f7cfb38da241df0ba # v2.9.1 - with: - flutter-version: '3.47.2' - channel: stable - - name: Build packaged example - working-directory: example - run: | - case "${{ matrix.platform }}" in - linux) flutter build linux --release ;; - android) flutter build apk --release --target-platform android-arm64 ;; - ios) flutter build ios --release --no-codesign ;; - macos) flutter build macos --release ;; - windows) flutter build windows --release ;; - esac - - name: Load packaged macOS library - if: matrix.platform == 'macos' - run: bash tool/ci/check_macos_app.sh - - name: Load packaged Windows library - if: matrix.platform == 'windows' - run: python tool/ci/smoke_native.py example/build/windows/x64/runner/Release/tor_ffi_plugin.dll - - release: - if: github.event_name != 'pull_request' && github.event.deleted != true - needs: [verify, build] - runs-on: ubuntu-22.04 - permissions: - contents: write - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 - with: - pattern: prebuilt-* - path: artifacts - merge-multiple: true - - uses: subosito/flutter-action@1508160852fb97248640997f7cfb38da241df0ba # v2.9.1 - with: - flutter-version: '3.47.2' - channel: stable - - run: flutter pub get - - name: Generate manifest - run: dart --packages=.dart_tool/package_config.json tool/prebuilt_manifest.dart artifacts - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: native-release - path: artifacts/* - if-no-files-found: error - - name: Publish release - if: startsWith(github.ref, 'refs/tags/native-') - env: - GH_TOKEN: ${{ github.token }} - RELEASE_TAG: ${{ github.ref_name }} - shell: bash - run: | - [[ "$RELEASE_TAG" =~ ^native-[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || exit 1 - read -r manifest_pin _ < artifacts/manifest.sha256 - cat > "$RUNNER_TEMP/release-notes.md" <> "$GITHUB_OUTPUT" + - name: Choose the build mode + shell: bash + run: echo '${{ steps.release.outputs.published == 'true' && 'prebuilt' || 'source' }}' > .dnix-build + - name: Run flutter test + if: runner.os != 'Windows' || steps.release.outputs.published == 'true' + shell: bash + run: | + if ! flutter test 2>&1 | tee test.log; then + echo "::error::$(tail -c 1500 test.log | tr '\n' ' ')" + exit 1 + fi + - name: Test example widgets + if: runner.os == 'Linux' && runner.arch == 'X64' + working-directory: example + run: flutter test + + # The crate's own checks, as before the native library moved to dnix. + rust: + name: Rust tests, lints, and audit + runs-on: ubuntu-24.04 + timeout-minutes: 60 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install Linux prerequisites + run: | + sudo apt-get update + sudo apt-get install -y build-essential pkg-config perl + - name: Install pinned Rust toolchain + run: bash tool/ci/install_rust.sh --component clippy,rustfmt + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 + with: + workspaces: rust + - name: Verify Rust + run: | + cargo fmt --manifest-path rust/Cargo.toml --check + cargo test --locked --all-targets --manifest-path rust/Cargo.toml + cargo clippy --locked --all-targets --manifest-path rust/Cargo.toml -- -D warnings + - name: Cache pinned cargo-audit binary + id: cargo-audit-cache + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: ~/.cargo/bin/cargo-audit + key: cargo-audit-${{ runner.os }}-${{ runner.arch }}-0.22.2-${{ hashFiles('rust/rust-toolchain.toml') }} + - name: Install pinned cargo-audit + if: steps.cargo-audit-cache.outputs.cache-hit != 'true' + run: cargo install cargo-audit --version 0.22.2 --locked + # RUSTSEC-2026-0387 is crypto-bigint 0.5, which only the optional ECDSA + # support of ssh-key-fork-arti pulls in: no feature arti enables + # compiles it (cargo tree finds no path to it), and the elliptic-curve + # 0.13 series it is locked by cannot take the fixed 0.7 series. Drop + # the ignore once arti moves to elliptic-curve 0.14. + - name: Audit Rust dependencies + run: | + cargo audit --file rust/Cargo.lock \ + --deny unsound \ + --deny yanked \ + --ignore RUSTSEC-2021-0145 \ + --ignore RUSTSEC-2023-0071 \ + --ignore RUSTSEC-2026-0387 + + windows-tests: + if: github.event_name == 'pull_request' + name: Windows time, IPv6, and file release tests + runs-on: windows-2025 + timeout-minutes: 60 + env: + NATIVE_TARGET: x86_64-pc-windows-msvc + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Use short Windows build paths + shell: pwsh + run: ./tool/ci/windows_paths.ps1 + - name: Install pinned Rust toolchain + run: bash tool/ci/install_rust.sh --target "$NATIVE_TARGET" + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 + with: + workspaces: rust + key: windows-tests + cache-targets: false + - name: Test time limits, IPv6 fallback, and file release + run: bash tool/ci/test_windows.sh + diff --git a/.gitignore b/.gitignore index cab43c36..4956b3db 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ rust/target/CACHEDIR.TAG # Cargo output from building a vendored crate on its own. rust/patches/*/target/ rust/patches/*/Cargo.lock + +# Per-checkout dnix build mode override (see README). +.dnix-build diff --git a/CHANGELOG.md b/CHANGELOG.md index c91fa616..d1bc05f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +## Unreleased + +* **Breaking change:** the native library now comes from + [dnix](https://github.com/cypherstack/dnix). By default the build hook + downloads the library of the release `dnix.lock` names and verifies its + SHA-256; with the `build` user define set to `source` it builds the same + library with Nix, on Linux or an Apple Silicon Mac. The `native_build`, + `prebuilt_manifest_url` and `prebuilt_manifest_sha256` user defines are + no longer read: pinning this package's commit pins its libraries. +* Every change is built on four hosts, which must produce the bytes + `dnix.lock` records; the default branch publishes the release it names. +* Bump tokio to 1.54.1, async-compression to 0.4.51 (zstd 0.14 with + zstd-safe 8.2.0, liblzma in place of xz2) and ctutils to 0.4.7, which + clears the RustSec advisories against tokio 1.50, zstd-safe 7.1, zstd-sys + 2.0 and ctutils 0.4.2. The statically linked xz library now comes from + liblzma-sys, the crate the new async-compression uses. The audit ignores + RUSTSEC-2026-0387 (crypto-bigint 0.5), an optional dependency no enabled + feature compiles. + ## 0.2.0 * Update the example to socks_socket 2.0.0, await Tor shutdown, and show start diff --git a/README.md b/README.md index 5c767157..7131a002 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,10 @@ SPDX-License-Identifier: GPL-3.0-or-later ## Getting started -Source builds are the default. To use prebuilt libraries without installing Rust, -see [Prebuilt libraries](#prebuilt-libraries). - -### [Install rust](https://www.rust-lang.org/tools/install) - -Use `rustup`, not `homebrew`. The toolchain and cross-compilation targets pinned in `rust/rust-toolchain.toml` are installed automatically on first build. - -### Building - -The Rust library is built and bundled by Flutter's native assets support (see `hook/build.dart`), so just `flutter run` or build as usual. Requires Flutter 3.47.2 or later; Android builds need NDK 27 or later. +Requires Flutter 3.47.2 or later. Nothing else is needed to use the package: +the build hook downloads the Rust library built for this version and checks +its hash before Flutter bundles it, so `flutter run` and `flutter build` work +as usual. See [Native library](#native-library) for building it yourself. ### Starting and stopping Tor @@ -68,48 +62,59 @@ If startup and its cleanup both fail, the original startup exception and stack are preserved. The cleanup failure is printed with its stack and remains available as `lastCleanupError` and `lastCleanupStackTrace` until the next native start attempt. -### Prebuilt libraries +### Native library -Copy the manifest URL and SHA-256 from a native release into your application's -root `pubspec.yaml` (the workspace root for pub workspaces): +The hook obtains the Rust library in one of three modes, chosen with the +`build` user define in the consuming app's root `pubspec.yaml` (the workspace +root for pub workspaces), or for this checkout with a `.dnix-build` file +holding the mode: ```yaml hooks: user_defines: tor_ffi_plugin: - native_build: prebuilt - prebuilt_manifest_url: https://github.com/cypherstack/tor/releases/download/native-YOUR-RELEASE/manifest.json - prebuilt_manifest_sha256: "REPLACE_WITH_RELEASE_MANIFEST_SHA256" + build: prebuilt # the default; or source, or dev ``` -Use the package revision named in the release. The hook verifies the manifest, -native source fingerprint, and library size and checksum before bundling it. -Missing or invalid downloads fail the build. Downloads require public HTTPS. -Flutter can reuse the hook output until `flutter clean` removes it. The normal -Flutter application build tools are still required. Omit these settings or use -`native_build: source` to compile Rust locally. +- `prebuilt` downloads the library of the release `dnix.lock` names and + verifies it against the hash recorded there. A mismatch is an error, never + a fallback. Downloads are cached by hash and need public HTTPS. +- `source` builds the library with Nix, the same build CI uses, and verifies + it against the same hash. Needs Nix on Linux or an Apple Silicon Mac; the + Apple targets need Xcode 27.0. +- `dev` builds with Nix but skips the Xcode pin and the hash check, for + working on the Rust code. The hook notices a `.dnix-build` file from its + next run on, so touch `dnix.json` after creating it. -Prebuilts use dynamic linking without sanitizers and cover: +Only dynamic libraries are provided, without sanitizers: | Platform | Architectures | Minimum | | --- | --- | --- | | Android | armv7, arm64, x64 | API 24; supports 16 KiB pages | | iOS | arm64 device; arm64/x64 simulator | iOS 13; arm64 simulator 14 | | macOS | arm64, x64 | macOS 11 | -| Linux | x64; arm64 | glibc 2.35 (x64); 2.39 (arm64) | +| Linux | x64, arm64 | glibc 2.28 | | Windows | x64 | MSVC runtime | -Push a new `native-*` tag (for example `native-0.1.0-1`) to build and publish -all targets. Release notes contain the hook settings above with the actual pin. -Existing releases are not overwritten. Manual workflow runs on branches only -upload artifacts. Pull requests run Rust/Dart tests, Windows time/IPv6/file-release -tests, and example packaging checks; the full cross-build matrix runs for tags -and manual dispatches. To generate the manifest locally, put the target-named -libraries in `artifacts/`, run `flutter pub get`, then: +The floors, the Rust and Xcode versions, and the location of the Rust +workspace live in `dnix.json`. -```sh -dart --packages=.dart_tool/package_config.json tool/prebuilt_manifest.dart artifacts -``` +#### Releases + +`dnix.lock` records the hash of every library the Rust source builds to and +names the GitHub release, `native-`, that holds those bytes. After +changing the Rust code, run `dart tool/update_lock.dart` on a Mac with Nix +and Xcode 27.0 and commit the lock with the change; without a Mac, push and +copy the lock CI prints once every host is green. CI +(`.github/workflows/native.yml`) builds on four hosts, fails if any host or +the lock disagrees, and publishes the release when the lock reaches the +default branch. Nothing is committed by CI, and old releases stay. + +Anyone can rebuild one platform with Nix and compare: +`dart tool/verify_release.dart linux` (or android, windows, macos, ios, or a +target triple), on Windows inside WSL2 for the non-Apple platforms. The build +comes from the shared [dnix](https://github.com/cypherstack/dnix) package, +whose documentation explains how the bytes stay identical across hosts. ## Development @@ -125,6 +130,10 @@ Run the Rust tests with the pinned toolchain, then the offline Dart tests: flutter test ``` +Before the lock's release is published, or to test local Rust changes, the +tests need the library built from source: `echo dev > .dnix-build` with Nix +installed (`source` to also check the hash). + The lifecycle regression also verifies stop/restart against the real Tor network. Enable it explicitly (it requires network access and can take several minutes): diff --git a/dnix.json b/dnix.json new file mode 100644 index 00000000..b5f8354d --- /dev/null +++ b/dnix.json @@ -0,0 +1,33 @@ +{ + "name": "tor_ffi_plugin", + "repository": "https://github.com/cypherstack/tor", + "library": "tor_ffi_plugin", + "asset": "tor_ffi_plugin_bindings_generated.dart", + "rust": { + "workspace": "rust", + "crate": "tor_ffi_plugin" + }, + "toolchains": { + "rust": "1.99.1", + "xcode": "27.0" + }, + "floors": { + "macos": 11, + "ios": 13, + "android": 24, + "glibc": "2.28" + }, + "targets": [ + "aarch64-apple-darwin", + "x86_64-apple-darwin", + "aarch64-apple-ios", + "aarch64-apple-ios-sim", + "x86_64-apple-ios", + "aarch64-linux-android", + "armv7-linux-androideabi", + "x86_64-linux-android", + "aarch64-unknown-linux-gnu", + "x86_64-unknown-linux-gnu", + "x86_64-pc-windows-msvc" + ] +} diff --git a/dnix.lock b/dnix.lock new file mode 100644 index 00000000..325a8407 --- /dev/null +++ b/dnix.lock @@ -0,0 +1,17 @@ +{ + "release": "bdccea9785ab", + "date": "2026-10-30", + "files": { + "aarch64-apple-darwin/libtor_ffi_plugin.dylib": "5ad0c311ed4da79247c8e2dc645d8a9944431f77c0f45fb5b750a919e2d45d2c", + "aarch64-apple-ios-sim/libtor_ffi_plugin.dylib": "b76b916c06922fa185d9b1e8e696d8814aeac00a412c5bd5208d9887035daaa4", + "aarch64-apple-ios/libtor_ffi_plugin.dylib": "31ce202a0354b1a018247aa514d8273021dbfe3f52b3498cd7e4f2f45b219906", + "aarch64-linux-android/libtor_ffi_plugin.so": "841172a1bac55d8bffef8c79341a60d72c28024db488637e440d7b5f92987aec", + "aarch64-unknown-linux-gnu/libtor_ffi_plugin.so": "36e26458a52921db137c49a08a01e650ba1a3cd57067a7a613724e473efe9897", + "armv7-linux-androideabi/libtor_ffi_plugin.so": "fd67eb4ed118d8d834f9b564e2d33a3336be17a0d58a20ba41fe5f51ae31246c", + "x86_64-apple-darwin/libtor_ffi_plugin.dylib": "828edc5ead111d7441eef4f314fa71307031a95cfb433e32987fc59f81faf529", + "x86_64-apple-ios/libtor_ffi_plugin.dylib": "37cb0a4686e6b943099b04a6d32e8648b2861b004edf75e9c041e93dea61818c", + "x86_64-linux-android/libtor_ffi_plugin.so": "1bd6a8ac0df51b0c479d6c831a5ae790d720de2b82b18830c8755067001b43b4", + "x86_64-pc-windows-msvc/tor_ffi_plugin.dll": "269fc580023948afc0be640d19bbc419a8cb49689c071702de6f404b5d4feaf1", + "x86_64-unknown-linux-gnu/libtor_ffi_plugin.so": "63d545aa4840766791043f8e55c48e30c237445499600e1fdf661d07f370e12c" + } +} diff --git a/example/pubspec.lock b/example/pubspec.lock index 2252ad0d..6cf19144 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -73,6 +73,15 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.9" + dnix: + dependency: transitive + description: + path: "." + ref: e91763ef25d084b3a5cf271a0cfee69159b1f779 + resolved-ref: e91763ef25d084b3a5cf271a0cfee69159b1f779 + url: "https://github.com/cypherstack/dnix.git" + source: git + version: "0.1.0" fake_async: dependency: transitive description: @@ -203,14 +212,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.0" - native_toolchain_rust: - dependency: transitive - description: - name: native_toolchain_rust - sha256: "8469a0e32a47a5c84b7fd300de9fd0e8082aac76c37922986324f5082a58350d" - url: "https://pub.dev" - source: hosted - version: "1.0.6" objective_c: dependency: transitive description: @@ -283,14 +284,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" - url: "https://pub.dev" - source: hosted - version: "7.0.2" platform: dependency: transitive description: @@ -392,14 +385,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.12" - toml: - dependency: transitive - description: - name: toml - sha256: "35a35f782228656a2af31e8c73d1353cc4ef3d683fd68af1111b44631879c05e" - url: "https://pub.dev" - source: hosted - version: "0.18.0" tor_ffi_plugin: dependency: "direct main" description: @@ -431,14 +416,6 @@ packages: url: "https://pub.dev" source: hosted version: "15.3.0" - web: - dependency: transitive - description: - name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" - source: hosted - version: "1.1.1" xdg_directories: dependency: transitive description: diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..928b9315 --- /dev/null +++ b/flake.lock @@ -0,0 +1,123 @@ +{ + "nodes": { + "dnix": { + "inputs": { + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay", + "zig-overlay": "zig-overlay" + }, + "locked": { + "lastModified": 1793124403, + "narHash": "sha256-Nf8+4VKQWYAE7telMnxGa2no18Yu4BemMZJ2PCeG+II=", + "owner": "cypherstack", + "repo": "dnix", + "rev": "e91763ef25d084b3a5cf271a0cfee69159b1f779", + "type": "github" + }, + "original": { + "owner": "cypherstack", + "repo": "dnix", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1792502884, + "narHash": "sha256-IoNisKcGbUjdd45qCqywGMCYmZ5EnkxezvLII0FWwNA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "02c087f3fe673c9e317f206a627cb6b910336dbe", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "dnix": "dnix" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "dnix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1792567368, + "narHash": "sha256-mrM+zFDRO1pI85t2SEq5R739BIq7Nv5XkFtVZK9nkZM=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "83dd732f2428827f35d997deae108a2ced5125f4", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "flake": false, + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "zig-overlay": { + "inputs": { + "flake-compat": "flake-compat", + "nixpkgs": [ + "dnix", + "nixpkgs" + ], + "systems": "systems" + }, + "locked": { + "lastModified": 1792585651, + "narHash": "sha256-Kg8Qdi6ur+K5hNJwav0pEqdylGd7vXxEPeBvaKW5nxA=", + "owner": "mitchellh", + "repo": "zig-overlay", + "rev": "98819174f1bb7c9961c3ebbb00d19fc4e1c1960d", + "type": "github" + }, + "original": { + "owner": "mitchellh", + "repo": "zig-overlay", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..8884b8f1 --- /dev/null +++ b/flake.nix @@ -0,0 +1,10 @@ +{ + description = "Reproducible reference builds of tor_ffi_plugin's Rust library"; + + inputs.dnix.url = "github:cypherstack/dnix"; + + outputs = { self, dnix, ... }: dnix.lib.package { + src = self; + config = ./dnix.json; + }; +} diff --git a/hook/build.dart b/hook/build.dart index 19d6fd1c..7997f795 100644 --- a/hook/build.dart +++ b/hook/build.dart @@ -1,19 +1,3 @@ -import 'package:code_assets/code_assets.dart'; -import 'package:hooks/hooks.dart'; -import 'package:native_toolchain_rust/native_toolchain_rust.dart'; +import 'package:dnix/dnix.dart'; -import 'prebuilt.dart'; - -Future main(List args) async { - await build(args, (input, output) async { - if (!input.config.buildCodeAssets) return; - if (await usePrebuilt(input, output)) return; - - output.dependencies.addAll(await sourceDependencies(input.packageRoot)); - - await const RustBuilder( - assetName: 'tor_ffi_plugin_bindings_generated.dart', - extraCargoBuildArgs: ['--locked'], - ).run(input: input, output: output); - }); -} +void main(List args) => buildNativeLibrary(args); diff --git a/hook/prebuilt.dart b/hook/prebuilt.dart deleted file mode 100644 index b6c321b8..00000000 --- a/hook/prebuilt.dart +++ /dev/null @@ -1,292 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:code_assets/code_assets.dart'; -import 'package:crypto/crypto.dart'; -import 'package:hooks/hooks.dart'; - -// Deployment floors used by the release workflow; zero means not applicable. -const releaseTargets = { - 'armv7-linux-androideabi': 24, - 'aarch64-linux-android': 24, - 'x86_64-linux-android': 24, - 'aarch64-apple-ios': 13, - 'aarch64-apple-ios-sim': 13, - 'x86_64-apple-ios': 13, - 'aarch64-apple-darwin': 11, - 'x86_64-apple-darwin': 11, - 'aarch64-unknown-linux-gnu': 0, - 'x86_64-unknown-linux-gnu': 0, - 'x86_64-pc-windows-msvc': 0, -}; - -String libraryName(String target) => target.contains('windows') - ? 'tor_ffi_plugin.dll' - : 'libtor_ffi_plugin.${target.contains('apple') ? 'dylib' : 'so'}'; - -String releaseFileName(String target) => '$target-${libraryName(target)}'; - -Future fileHash(File file) async => - (await sha256.bind(file.openRead()).first).toString(); - -/// Native source files tracked as a whole. -const _sourceFiles = [ - 'rust/Cargo.toml', - 'rust/Cargo.lock', - 'rust/rust-toolchain.toml', - 'rust/build.rs', - 'hook/build.dart', - 'lib/tor_ffi_plugin_bindings_generated.dart', -]; - -/// Native source trees whose entries are tracked recursively. -const _sourceDirectories = ['rust/src/', 'rust/patches/']; - -/// Native inputs to track for Flutter's hook cache. -/// -/// Directories are included so additions and deletions invalidate the build. -/// Hidden entries are not native inputs and are skipped. -Future> sourceDependencies(Uri root) async => [ - for (final path in [..._sourceFiles, ..._sourceDirectories]) - root.resolve(path), - for (final path in _sourceDirectories) - ...await _sourceEntries(Directory.fromUri(root.resolve(path))), -]..sort((a, b) => a.path.compareTo(b.path)); - -// Hash paths and contents so additions, deletions and ABI changes invalidate -// prebuilts. -Future<({String hash, List dependencies})> sourceFingerprint( - Uri root, -) async { - final dependencies = await sourceDependencies(root); - final hashes = StringBuffer(); - for (final uri in dependencies) { - final type = await FileSystemEntity.type( - uri.toFilePath(), - followLinks: false, - ); - if (type == FileSystemEntityType.link) { - throw StateError('Native source symlinks are unsupported: $uri'); - } - if (type == FileSystemEntityType.directory) continue; - // Git checkouts may use CRLF on Windows. Hash bytes rather than decoded - // text so a file that is not UTF-8 cannot fail the build. - final bytes = _lfLineEndings(await File.fromUri(uri).readAsBytes()); - hashes.writeln( - '${uri.path.substring(root.path.length)}:${sha256.convert(bytes)}', - ); - } - return ( - hash: sha256.convert(utf8.encode(hashes.toString())).toString(), - dependencies: dependencies, - ); -} - -/// Entries under [directory], without descending into skipped directories. -Future> _sourceEntries(Directory directory) async => [ - await for (final entry in directory.list(followLinks: false)) - if (!_isSkipped(entry)) ...[ - entry.uri, - if (entry is Directory) ...await _sourceEntries(entry), - ], -]; - -/// Whether [entry] is hidden, such as .DS_Store, or Cargo output left by -/// building a vendored crate on its own: its `target` directory, or its -/// lockfile, which Cargo ignores for dependencies. -bool _isSkipped(FileSystemEntity entry) { - final name = entry.uri.pathSegments.lastWhere((s) => s.isNotEmpty); - if (name.startsWith('.') || name == 'Cargo.lock') return true; - return entry is Directory && - name == 'target' && - File.fromUri(entry.parent.uri.resolve('Cargo.toml')).existsSync(); -} - -/// [bytes] with each CRLF replaced by LF. -List _lfLineEndings(List bytes) { - if (!bytes.contains(0x0d)) return bytes; - return [ - for (var i = 0; i < bytes.length; i++) - if (bytes[i] != 0x0d || i + 1 == bytes.length || bytes[i + 1] != 0x0a) - bytes[i], - ]; -} - -String rustTarget(CodeConfig code) => switch (( - code.targetOS, - code.targetArchitecture, -)) { - (OS.android, Architecture.arm) => 'armv7-linux-androideabi', - (OS.android, Architecture.arm64) => 'aarch64-linux-android', - (OS.android, Architecture.x64) => 'x86_64-linux-android', - (OS.iOS, Architecture.arm64) => - code.iOS.targetSdk == IOSSdk.iPhoneSimulator - ? 'aarch64-apple-ios-sim' - : 'aarch64-apple-ios', - (OS.iOS, Architecture.x64) - when code.iOS.targetSdk == IOSSdk.iPhoneSimulator => - 'x86_64-apple-ios', - (OS.macOS, Architecture.arm64) => 'aarch64-apple-darwin', - (OS.macOS, Architecture.x64) => 'x86_64-apple-darwin', - (OS.linux, Architecture.arm64) => 'aarch64-unknown-linux-gnu', - (OS.linux, Architecture.x64) => 'x86_64-unknown-linux-gnu', - (OS.windows, Architecture.x64) => 'x86_64-pc-windows-msvc', - _ => throw UnsupportedError( - 'No prebuilt for ${code.targetOS}/${code.targetArchitecture}. Use native_build: source.', - ), -}; - -String _digest(Object? value) { - if (value is! String || !RegExp(r'^[a-fA-F0-9]{64}$').hasMatch(value)) { - throw FormatException('Expected a SHA-256 digest (64 hex characters).'); - } - return value.toLowerCase(); -} - -Future _download( - HttpClient client, - Uri url, - File file, - String hash, - int limit, -) async { - for (var redirects = 0; redirects <= 5; redirects++) { - if (url.scheme != 'https' || - url.host.isEmpty || - url.userInfo.isNotEmpty || - url.hasFragment) { - throw FormatException( - 'Prebuilt URLs require HTTPS without credentials or fragments.', - ); - } - final request = await client - .getUrl(url) - .timeout(const Duration(seconds: 30)); - request.followRedirects = false; - final response = await request.close().timeout(const Duration(seconds: 30)); - if ([301, 302, 303, 307, 308].contains(response.statusCode)) { - final location = response.headers.value(HttpHeaders.locationHeader); - await response.listen(null).cancel(); - if (location == null) { - throw HttpException('Missing redirect location', uri: url); - } - url = url.resolve(location); - continue; - } - if (response.statusCode != HttpStatus.ok || - response.contentLength > limit) { - await response.listen(null).cancel(); - throw HttpException( - 'Invalid prebuilt response: HTTP ${response.statusCode}', - uri: url, - ); - } - final sink = file.openWrite(); - var received = 0; - try { - await sink.addStream( - response.timeout(const Duration(seconds: 30)).map((bytes) { - received += bytes.length; - if (received > limit) { - throw StateError('Prebuilt exceeds expected size.'); - } - return bytes; - }), - ); - } finally { - await sink.close(); - } - if (await fileHash(file) != hash) { - throw StateError('Checksum mismatch: $url'); - } - return; - } - throw HttpException('Too many prebuilt redirects', uri: url); -} - -Future usePrebuilt(BuildInput input, BuildOutputBuilder output) async { - final mode = input.userDefines['native_build'] ?? 'source'; - if (mode == 'source') return false; - if (mode != 'prebuilt') { - throw FormatException('native_build must be source or prebuilt.'); - } - final url = input.userDefines['prebuilt_manifest_url']; - if (url is! String) throw FormatException('Missing prebuilt_manifest_url.'); - final pin = _digest(input.userDefines['prebuilt_manifest_sha256']); - final manifestUrl = Uri.parse(url); - final code = input.config.code; - if (code.linkModePreference == LinkModePreference.static || - code.sanitizer != null) { - throw UnsupportedError( - 'Prebuilts require dynamic linking without sanitizers.', - ); - } - final target = rustTarget(code); - final minimum = switch (code.targetOS) { - OS.android => code.android.targetNdkApi, - OS.iOS => code.iOS.targetVersion, - OS.macOS => code.macOS.targetVersion, - _ => 0, - }; - if (minimum < releaseTargets[target]!) { - throw UnsupportedError( - '$target prebuilts require OS/API ${releaseTargets[target]}.', - ); - } - final source = await sourceFingerprint(input.packageRoot); - output.dependencies.addAll(source.dependencies); - final directory = await Directory.fromUri(input.outputDirectory) - .create(recursive: true); - final temporary = await directory.createTemp('prebuilt-'); - final client = HttpClient()..connectionTimeout = const Duration(seconds: 30); - try { - final manifestFile = File.fromUri(temporary.uri.resolve('manifest.json')); - await _download(client, manifestUrl, manifestFile, pin, 1024 * 1024); - final manifest = - jsonDecode(await manifestFile.readAsString()) as Map; - if (manifest['schema_version'] != 1 || - manifest['package'] != 'tor_ffi_plugin') { - throw FormatException('Unsupported tor_ffi_plugin manifest.'); - } - if (manifest['source_sha256'] != source.hash) { - throw StateError( - 'Prebuilt source mismatch. Use the matching package revision or native_build: source.', - ); - } - final artifact = (manifest['artifacts'] as Map)[target]; - if (artifact is! Map) { - throw StateError('Missing prebuilt for $target.'); - } - final hash = _digest(artifact['sha256']); - final size = artifact['size']; - if (size is! int || size <= 0 || size > 1024 * 1024 * 1024) { - throw FormatException('Invalid prebuilt size for $target.'); - } - final library = File.fromUri(temporary.uri.resolve(libraryName(target))); - await _download( - client, - manifestUrl.resolve(releaseFileName(target)), - library, - hash, - size, - ); - if (await library.length() != size) { - throw StateError('Prebuilt size mismatch.'); - } - final bundled = await library.copy( - directory.uri.resolve(libraryName(target)).toFilePath(), - ); - output.assets.code.add( - CodeAsset( - package: input.packageName, - name: 'tor_ffi_plugin_bindings_generated.dart', - linkMode: DynamicLoadingBundled(), - file: bundled.uri, - ), - ); - return true; - } finally { - client.close(force: true); - await temporary.delete(recursive: true); - } -} diff --git a/pubspec.yaml b/pubspec.yaml index ec548808..4808ea9f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,10 +12,12 @@ dependencies: sdk: flutter ffi: ^2.0.1 code_assets: ^1.2.1 - crypto: ^3.0.7 hooks: ^2.0.2 meta: ^1.16.0 - native_toolchain_rust: ^1.0.6 + dnix: + git: + url: https://github.com/cypherstack/dnix.git + ref: e91763ef25d084b3a5cf271a0cfee69159b1f779 dev_dependencies: ffigen: ^21.0.0 diff --git a/rust/Cargo.lock b/rust/Cargo.lock index fbe410ab..f7c8051c 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -3,10 +3,10 @@ version = 4 [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aes" @@ -313,18 +313,14 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "async-compression" -version = "0.4.11" +version = "0.4.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5" +checksum = "9d5755640120b24961f8231e05892e4a4f7e0d04e8aeca161508c9c397946970" dependencies = [ - "flate2", - "futures-core", + "compression-codecs", + "compression-core", "futures-io", - "memchr", "pin-project-lite", - "xz2", - "zstd", - "zstd-safe", ] [[package]] @@ -719,6 +715,25 @@ dependencies = [ "memchr", ] +[[package]] +name = "compression-codecs" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cae36683101b2899f48bd31f8db9839437356188e3b1ff23096e73dab09ba8fe" +dependencies = [ + "compression-core", + "flate2", + "liblzma", + "zstd", + "zstd-safe", +] + +[[package]] +name = "compression-core" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ebdda89edc534bf8d5dd2857562633b6495557e5846552ade994b99ed13c36a" + [[package]] name = "const-oid" version = "0.9.6" @@ -864,9 +879,9 @@ dependencies = [ [[package]] name = "ctutils" -version = "0.4.2" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +checksum = "a2376ffd6f96d8a9db59875b4b6b0237d6900b5baf64279714e05493ce64828a" dependencies = [ "cmov", ] @@ -1094,7 +1109,7 @@ dependencies = [ "quote", "sha3", "strum 0.28.0", - "syn 2.0.105", + "syn 3.0.5", "unicode-ident", "void", ] @@ -1213,7 +1228,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1460,12 +1475,13 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.30" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -2248,6 +2264,26 @@ version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +[[package]] +name = "liblzma" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa980ea6744074a5b5e0ac6b164aca94fc970121869b3e78ae12a2b0097dbea7" +dependencies = [ + "liblzma-sys", +] + +[[package]] +name = "liblzma-sys" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c7e3581f367a78d7b7e7ae948d023310556f0cfc13156c2e4e00e25616492b9" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "libm" version = "0.2.8" @@ -2309,17 +2345,6 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" -[[package]] -name = "lzma-sys" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - [[package]] name = "matchers" version = "0.2.0" @@ -2364,23 +2389,24 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" dependencies = [ - "adler", + "adler2", + "simd-adler32", ] [[package]] name = "mio" -version = "1.0.4" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2454,7 +2480,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -3580,6 +3606,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + [[package]] name = "simd_cesu8" version = "1.2.0" @@ -3646,7 +3678,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -3993,9 +4025,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.50.0" +version = "1.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" dependencies = [ "bytes", "libc", @@ -4010,13 +4042,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 3.0.5", ] [[package]] @@ -5229,8 +5261,8 @@ dependencies = [ "glob", "lazy_static", "libc", + "liblzma-sys", "log", - "lzma-sys", "rlimit 0.10.1", "saturating-time", "security-framework", @@ -6094,15 +6126,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "xz2" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" -dependencies = [ - "lzma-sys", -] - [[package]] name = "yoke" version = "0.8.0" @@ -6201,29 +6224,35 @@ dependencies = [ "syn 2.0.105", ] +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + [[package]] name = "zstd" -version = "0.13.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" +checksum = "db3ae9bf90af6e40197bef767eeefc46ad6397420fe873830bd85e5fbbfb3c70" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.1.0" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +checksum = "ded520f16b29e1a9b771c722221b2b0a0398c2316d9bb3c42b458a5414d7434b" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.11+zstd.1.5.6" +version = "2.1.2+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75652c55c0b6f3e6f12eb786fe1bc960396bf05a1eb3bf1f3691c3610ac2e6d4" +checksum = "481ada9c543b2323cbb7bbdbb5249177fbf0188ed959ed2c15e4b22421c84ed3" dependencies = [ "cc", "pkg-config", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 9ac2bcbf..90136417 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -19,7 +19,7 @@ arti-client = { version = "0.47.0", features = ["static", "onion-service-client" arti = { version = "2.7.0", features = ["experimental-api", "static"] } tor-rtcompat = { version = "0.47.0", features = ["static", "native-tls", "tokio"] } log = "0.4.20" -lzma-sys = { version = "0.1.20", features = ["static"] } +liblzma-sys = { version = "0.4.9", features = ["static"] } #android_log-sys = "0.3.1" rlimit = "0.10.1" anyhow = "1.0.79" diff --git a/test/source_fingerprint_test.dart b/test/source_fingerprint_test.dart deleted file mode 100644 index e25de4f5..00000000 --- a/test/source_fingerprint_test.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'dart:io'; - -import 'package:flutter_test/flutter_test.dart'; - -import '../hook/prebuilt.dart'; - -/// A minimal package tree containing every tracked native input. -Future sourceTree() async { - final root = await Directory.systemTemp.createTemp('tor-sources-'); - addTearDown(() => root.delete(recursive: true)); - for (final path in [ - 'rust/Cargo.toml', - 'rust/Cargo.lock', - 'rust/rust-toolchain.toml', - 'rust/build.rs', - 'hook/build.dart', - 'lib/tor_ffi_plugin_bindings_generated.dart', - 'rust/src/lib.rs', - 'rust/patches/saturating-time/Cargo.toml', - 'rust/patches/saturating-time/src/lib.rs', - ]) { - final file = File.fromUri(root.uri.resolve(path)); - await file.parent.create(recursive: true); - await file.writeAsString(path); - } - return root; -} - -void main() { - test('source dependencies are reusable without calculating hashes', () async { - final root = await sourceTree(); - - final dependencies = await sourceDependencies(root.uri); - final fingerprint = await sourceFingerprint(root.uri); - - expect(dependencies, fingerprint.dependencies); - expect(dependencies, contains(root.uri.resolve('rust/src/lib.rs'))); - expect(fingerprint.hash, hasLength(64)); - }); - - test('a change to a vendored patch changes the fingerprint', () async { - final root = await sourceTree(); - const patched = 'rust/patches/saturating-time/src/lib.rs'; - final before = await sourceFingerprint(root.uri); - - expect(before.dependencies, contains(root.uri.resolve(patched))); - await File.fromUri(root.uri.resolve(patched)).writeAsString('changed'); - final after = await sourceFingerprint(root.uri); - - expect(after.hash, isNot(before.hash)); - }); - - test('CRLF line endings do not change the fingerprint', () async { - final root = await sourceTree(); - final file = File.fromUri(root.uri.resolve('rust/src/lib.rs')); - await file.writeAsBytes('a\nb\r\nc\r'.codeUnits); - final lf = await sourceFingerprint(root.uri); - await file.writeAsBytes('a\r\nb\r\nc\r'.codeUnits); - final crlf = await sourceFingerprint(root.uri); - - expect(crlf.hash, lf.hash); - }); - - test('cargo output beside a vendored patch is not an input', () async { - final root = await sourceTree(); - final before = await sourceFingerprint(root.uri); - - // What `cargo test` leaves behind when run inside the vendored crate. - const crate = 'rust/patches/saturating-time/'; - final target = Directory.fromUri(root.uri.resolve('${crate}target/debug/')); - await target.create(recursive: true); - await File.fromUri(target.uri.resolve('libsaturating_time.rlib')) - .writeAsString('built'); - await File.fromUri(root.uri.resolve('${crate}Cargo.lock')) - .writeAsString('# lock'); - final after = await sourceFingerprint(root.uri); - - expect(after.hash, before.hash); - expect(after.dependencies, isNot(contains(target.uri))); - // The package's own lockfile is still an input. - expect(after.dependencies, contains(root.uri.resolve('rust/Cargo.lock'))); - }); -} diff --git a/tool/ci/check_macos_app.sh b/tool/ci/check_macos_app.sh deleted file mode 100644 index b6374da4..00000000 --- a/tool/ci/check_macos_app.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -# Check that the packaged macOS example's library exports and loads. -set -euo pipefail -cd "$(dirname "$0")/../.." -library=$(find -L example/build/macos/Build/Products/Release \ - -type f \ - -path '*/Contents/Frameworks/tor_ffi_plugin.framework/tor_ffi_plugin' \ - -print -quit) -test -n "$library" -nm -gj "$library" | grep -Fx _tor_proxy_stop -python3 tool/ci/smoke_native.py "$library" diff --git a/tool/ci/smoke_native.py b/tool/ci/smoke_native.py deleted file mode 100644 index ec7cd6ea..00000000 --- a/tool/ci/smoke_native.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Load an exact artifact and check its null-safe proxy-stop entry point.""" - -import ctypes -import sys -from pathlib import Path - -library = ctypes.CDLL(str(Path(sys.argv[1]).resolve(strict=True))) -library.tor_proxy_stop.argtypes = [ctypes.c_void_p] -library.tor_proxy_stop.restype = None -library.tor_proxy_stop(None) -print(f"Loaded native library: {sys.argv[1]}") diff --git a/tool/prebuilt_manifest.dart b/tool/prebuilt_manifest.dart deleted file mode 100644 index e0b64caf..00000000 --- a/tool/prebuilt_manifest.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import '../hook/prebuilt.dart'; - -// Run with dart --packages=.dart_tool/package_config.json to avoid build hooks. -Future main(List args) async { - if (args.length != 1) { - throw ArgumentError('Usage: prebuilt_manifest.dart '); - } - final directory = Directory(args.single).absolute; - final artifacts = {}; - for (final target in releaseTargets.keys) { - final file = File.fromUri(directory.uri.resolve(releaseFileName(target))); - final size = await file.length(); - if (size <= 0 || size > 1024 * 1024 * 1024) { - throw StateError('Invalid prebuilt size: ${file.path}'); - } - artifacts[target] = {'sha256': await fileHash(file), 'size': size}; - } - final source = await sourceFingerprint(Platform.script.resolve('../')); - final manifest = File.fromUri(directory.uri.resolve('manifest.json')); - final contents = { - 'schema_version': 1, - 'package': 'tor_ffi_plugin', - 'source_sha256': source.hash, - 'artifacts': artifacts, - }; - await manifest.writeAsString( - '${const JsonEncoder.withIndent(' ').convert(contents)}\n', - ); - await File.fromUri(directory.uri.resolve('manifest.sha256')) - .writeAsString('${await fileHash(manifest)} manifest.json\n'); -} diff --git a/tool/update_lock.dart b/tool/update_lock.dart new file mode 100644 index 00000000..73089196 --- /dev/null +++ b/tool/update_lock.dart @@ -0,0 +1,4 @@ +import 'dart:io'; +import 'package:dnix/tools.dart'; + +void main(List args) async => exit(await updateLock(args)); diff --git a/tool/verify_release.dart b/tool/verify_release.dart new file mode 100644 index 00000000..0dd9d24c --- /dev/null +++ b/tool/verify_release.dart @@ -0,0 +1,4 @@ +import 'dart:io'; +import 'package:dnix/tools.dart'; + +void main(List args) async => exit(await verifyRelease(args));