diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock deleted file mode 100644 index e2a1283..0000000 --- a/.claude/scheduled_tasks.lock +++ /dev/null @@ -1 +0,0 @@ -{"sessionId":"028e1ae6-6a0f-4c64-a51b-0758cdc98df0","pid":2822573,"procStart":"513810637","acquiredAt":1781697316875} \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4494b36..bdaed9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,7 @@ jobs: - uses: dtolnay/rust-toolchain@62ae3a85dbdd2bedbb5819da8ce45635129289a1 # 1.98.0 with: components: clippy - - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 - run: cargo clippy --locked --workspace --all-targets -- -D warnings test: @@ -58,7 +58,7 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@62ae3a85dbdd2bedbb5819da8ce45635129289a1 # 1.98.0 - - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 - run: cargo test --locked --workspace --exclude dpp-python test-features: @@ -68,7 +68,7 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@62ae3a85dbdd2bedbb5819da8ce45635129289a1 # 1.98.0 - - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 - run: cargo test --locked -p pbzx --features parallel - run: cargo test --locked -p dpp --features parallel @@ -123,8 +123,8 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@62ae3a85dbdd2bedbb5819da8ce45635129289a1 # 1.98.0 - - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 + - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ matrix.python-version }} @@ -156,7 +156,7 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@62ae3a85dbdd2bedbb5819da8ce45635129289a1 # 1.98.0 - - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 - run: cargo doc --locked --workspace --no-deps --exclude dpp-python env: RUSTDOCFLAGS: -D warnings diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 7827355..8e77f07 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -66,7 +66,7 @@ jobs: manylinux: auto - name: Upload wheels - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: wheels-${{ matrix.target }} path: dist/*.whl @@ -85,7 +85,7 @@ jobs: args: --out dist --manifest-path dpp-python/Cargo.toml - name: Upload sdist - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: sdist path: dist/*.tar.gz @@ -98,7 +98,7 @@ jobs: attestations: write steps: - name: Download all artifacts - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: path: dist merge-multiple: true @@ -110,7 +110,7 @@ jobs: subject-path: dist/* - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1 + uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3 with: packages-dir: dist/ # PEP 740 attestations, shown on the PyPI release page. Currently the diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bf34a57..1e7ce42 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -66,7 +66,7 @@ jobs: with: subject-path: target/package/*.crate - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 if: github.event_name == 'push' with: name: crates @@ -145,7 +145,7 @@ jobs: # anything is uploaded. # Exchanges the OIDC token for a 30-minute credential, so no long-lived # CARGO_REGISTRY_TOKEN secret is needed. - - uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1 + - uses: rust-lang/crates-io-auth-action@3268eb413133e2960fa4ae0e963ca76380c54cd9 # v1.0.6 id: auth # The logic lives in ci/publish.sh, linted by the "Publish script" job: it diff --git a/.gitignore b/.gitignore index 38403f2..c8d1867 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ tests/ demo/*.cast __pycache__/ .env +.claude/scheduled_tasks.lock diff --git a/Cargo.lock b/Cargo.lock index 7a9918b..6f696e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,7 +84,7 @@ dependencies = [ [[package]] name = "apfs" -version = "0.4.1" +version = "0.5.0-dev" dependencies = [ "byteorder", "cmpfs", @@ -253,7 +253,7 @@ checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" [[package]] name = "cmpfs" -version = "0.1.0" +version = "0.1.1-dev" dependencies = [ "flate2", "lzfse_rust", @@ -401,7 +401,7 @@ dependencies = [ [[package]] name = "dpp" -version = "0.6.1" +version = "0.7.0-dev" dependencies = [ "apfs", "flate2", @@ -415,7 +415,7 @@ dependencies = [ [[package]] name = "dpp-python" -version = "0.4.1" +version = "0.5.0-dev" dependencies = [ "dpp", "pyo3", @@ -423,7 +423,7 @@ dependencies = [ [[package]] name = "dpp-tool" -version = "0.6.1" +version = "0.7.0-dev" dependencies = [ "apfs", "clap", @@ -540,7 +540,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hfsplus" -version = "0.4.0" +version = "0.5.0-dev" dependencies = [ "byteorder", "cmpfs", @@ -727,7 +727,7 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "pbzx" -version = "0.5.1" +version = "0.5.2-dev" dependencies = [ "byteorder", "cpio-archive", @@ -1176,7 +1176,7 @@ checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "udif" -version = "0.4.2" +version = "0.5.0-dev" dependencies = [ "byteorder", "bzip2", @@ -1346,7 +1346,7 @@ dependencies = [ [[package]] name = "xara" -version = "0.4.2" +version = "0.5.0-dev" dependencies = [ "base64", "byteorder", diff --git a/apfs/Cargo.toml b/apfs/Cargo.toml index 05f361c..699109c 100644 --- a/apfs/Cargo.toml +++ b/apfs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "apfs" -version = "0.4.1" +version = "0.5.0-dev" edition = "2024" description = "Read-only APFS (Apple File System) parser" license = "MIT" @@ -10,7 +10,7 @@ categories = ["parsing", "filesystem"] [dependencies] byteorder = "1.5" -cmpfs = { version = "0.1.0", path = "../cmpfs" } +cmpfs = { version = "0.1.1-dev", path = "../cmpfs" } thiserror = "2" [dev-dependencies] diff --git a/cmpfs/Cargo.toml b/cmpfs/Cargo.toml index c0a24fb..ef9ea6b 100644 --- a/cmpfs/Cargo.toml +++ b/cmpfs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cmpfs" -version = "0.1.0" +version = "0.1.1-dev" edition = "2024" description = "Read-only decoder for Apple decmpfs transparent filesystem compression" license = "MIT" diff --git a/docs-site/uniffi.html b/docs-site/uniffi.html deleted file mode 100644 index cad9a2c..0000000 --- a/docs-site/uniffi.html +++ /dev/null @@ -1,240 +0,0 @@ - - -
- - -Mozilla's toolkit for generating safe, multi-language bindings from a single Rust library — and exactly what happens under the hood.
- Context: evaluating it fordpp → Go bindings
-UniFFI is a tool from Mozilla (born out of the Firefox/Application-Services effort to ship one Rust core to iOS, Android, and desktop) that auto-generates the FFI glue needed to call a Rust library from other languages. You write your logic once in Rust, describe the interface, and UniFFI emits:
-extern "C" entry points compiled into your crate, andOfficially supported targets: KotlinSwiftPythonRuby. Additional targets exist as external bindings generators — including Go (see §9), C#, Kotlin-Multiplatform, and Dart.
- -Calling Rust from another language always has to cross a C ABI — that is the only calling convention every runtime agrees on. Hand-writing that boundary means manually authoring #[no_mangle] extern "C" functions, raw-pointer marshalling, string/array encoding, error codes, and a matching set of unsafe declarations on the other side. It is repetitive, easy to get subtly wrong, and must be re-done for every language.
UniFFI's bet: that glue is mechanical, so generate it. One interface description → correct, consistent glue for N languages, regenerated automatically whenever the API changes.
- -There are two ways to tell UniFFI about your API. Modern projects favor proc-macros; the original mechanism is a UDL file.
- -Annotate the real Rust directly — no separate file:
-// lib.rs
-#[derive(uniffi::Record)]
-pub struct DirEntry { pub name: String, pub size: u64 }
-
-#[derive(uniffi::Object)]
-pub struct Filesystem { /* … */ }
-
-#[uniffi::export]
-impl Filesystem {
- pub fn list_directory(&self, path: String)
- -> Result<Vec<DirEntry>, DppError> { /* … */ }
-}
-
-uniffi::setup_scaffolding!();
- A WebIDL-like schema file, kept in sync with the Rust:
-// dpp.udl
-dictionary DirEntry {
- string name;
- u64 size;
-};
-
-interface Filesystem {
- [Throws=DppError]
- sequence<DirEntry> list_directory(string path);
-};
- Pulled in via uniffi::include_scaffolding!("dpp") in build.rs.
Either way, UniFFI builds an internal component-interface metadata model (records, enums, objects, functions, errors, their types) that drives all code generation.
- -End to end, here is what runs and what it produces:
-So there are two generated artifacts: (1) the C-ABI surface inside your compiled Rust library, and (2) the per-language wrapper source that knows how to call it. Both come from the same metadata, which is why they never drift.
- -The scaffolding is ordinary (generated) Rust that UniFFI compiles into your crate. For each exported function it emits a #[no_mangle] pub extern "C" shim that:
RustBuffer — see §6);catch_unwind guard (a Rust panic must never unwind across the boundary);It also exports housekeeping symbols: a per-type free function, allocators for RustBuffer, a checksum/contract-version symbol so a mismatched binding refuses to load, and (in proc-macro mode) the metadata the generator reads. You never see or hand-edit this code.
Scalars (i32, f64, …) cross the boundary directly. Everything compound — strings, structs (records), enums, sequences, maps, optionals — is serialized into bytes and passed as a RustBuffer: a small { ptr, len, capacity } struct describing a Rust-allocated byte buffer.
The encoding is defined by the FfiConverter trait (and Lift/Lower/Read/Write helpers). Each side implements the same wire format:
| Type | Wire encoding |
|---|---|
i32 / u64 / f64 … | passed by value, no buffer |
String | length-prefixed UTF-8 in a RustBuffer |
record (struct) | fields written back-to-back in declaration order |
enum | u32 variant discriminant, then that variant's fields |
Option<T> | 1 flag byte, then T if present |
sequence<T> / map | i32 count, then each element encoded in turn |
object (interface) | an opaque handle, not serialized — see §7 |
Records are passed by value (copied through a RustBuffer). Objects (anything with methods and identity — e.g. a Filesystem or Pipeline) are passed by reference as an opaque handle: the foreign side holds an integer/pointer it cannot inspect, and every method call passes that handle back to Rust.
Lifetime is reference-counted. The Rust side keeps the object behind an Arc; the scaffolding exposes clone/free symbols. The generated binding wires the object's destructor in the host language to the free symbol:
deinitCleaner / explicit .destroy() (and AutoCloseable)__del__ / context managerruntime.SetFinalizer plus a generated Destroy() methodArc are two separate accountants. UniFFI bridges them, but use-after-Destroy on a handle is the failure mode you still have to respect — the same lifecycle care any handle-based API needs. It is bounded to object handles, not to data.Result<T, E> where E is a UniFFI error enum becomes a thrown exception (Kotlin/Swift/Python) or an idiomatic error return (Go). The scaffolding writes a status code + serialized error into an out-param; the binding turns it into the host idiom.async fn across the boundary by integrating the host's event loop with a Rust future poller. (Go support for async lags the core; verify before relying on it.)uniffi-bindgen-goGo is not an officially-maintained target. It is served by an external generator, NordSecurity/uniffi-bindgen-go, which plugs into UniFFI as a custom bindings backend. Practical realities:
-import "C" / cgo glue you'd otherwise hand-write, plus typed Go structs for your records and Destroy()-bearing wrappers for your objects.uniffi-rs version. You must keep the generator, the runtime, and your crate's UniFFI dependency in lockstep.This is the question that actually matters for dpp, whose selling point is a memory-safe Apple-format parser. Precise answer:
unsafe.unsafe surface is ~zero: the glue is generated and shared across every consumer of UniFFI.unsafe by nature — UniFFI generates that unsafe, it does not eliminate it.Arc) coexist; use-after-Destroy of a handle is still possible from host misuse.If the requirement is a hard memory boundary — where even a hypothetical Rust bug cannot touch Go's address space — no in-process FFI (UniFFI or hand-written) achieves that, because they share an address space by definition. That guarantee only comes from process isolation (a subprocess/IPC daemon) or a WASM sandbox (compile dpp to wasm32-wasi, run it under a wasm runtime). Those trade performance and ergonomics for a true isolation wall. UniFFI's actual claim is narrower and still valuable: minimize and standardize the unavoidable unsafe, and keep every line of your real logic safe.