feat(adr-0005): Rust for image-codec leaf decoders; resolve M0 Rust-vs-C++ decision (§15)
ADR-0005 records the decision deferred in ADR-0003: Rust (staticlib, C ABI) for the image-codec leaf decoders where the CVE history concentrates (OpenJPEG ~40 buffer-overflow CVEs incl. 2024, several 'as used in PDFium'); C++ throughout elsewhere. Spike F measures the build-friction cost empirically (~10s cargo build, one .a link, ASan-clean FFI) rather than estimating it. - docs/adr/0005-rust-leaf-decoders.md: decision + CVE survey + evidence - docs/adr/0003 + README: mark deferred portion superseded by ADR-0005 - spike/F_rust_ffi_probe: Rust staticlib leaf + C++ driver, bounded (ptr,len) FFI matching §7.2; builds clean under ASan+UBSan - .gitignore: ignore cargo target/ (keep Cargo.lock for reproducibility)
This commit is contained in:
parent
91ae2077fa
commit
067bb7c362
|
|
@ -19,6 +19,12 @@ compile_commands.json
|
||||||
vcpkg/
|
vcpkg/
|
||||||
vcpkg_installed/
|
vcpkg_installed/
|
||||||
|
|
||||||
|
# Rust / cargo (Spike F leaf-decoder probe, ADR-0005)
|
||||||
|
# Keep the Cargo.toml/lock + src; ignore the build output.
|
||||||
|
**/target/
|
||||||
|
**/*.rs.bk
|
||||||
|
Cargo.lock.bak
|
||||||
|
|
||||||
# OS junk
|
# OS junk
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
# ADR-0003 — Memory safety posture for the parser layers
|
# ADR-0003 — Memory safety posture for the parser layers
|
||||||
|
|
||||||
* **Status**: Accepted (decision on Rust-vs-C++ for L1/L2 leaf decoders is
|
* **Status**: Accepted. The Rust-vs-C++ decision deferred here is **resolved
|
||||||
*deferred to end of M0* per §15; this ADR records the posture that holds
|
by ADR-0005** (Rust for the image-codec leaf decoders; C++ elsewhere).
|
||||||
regardless of that decision)
|
|
||||||
* **Date**: 2025-07-25
|
* **Date**: 2025-07-25
|
||||||
* **Plan reference**: §7.2, §15
|
* **Plan reference**: §7.2, §15
|
||||||
|
* **Superseded by**: the deferred-decision portion is superseded by ADR-0005;
|
||||||
|
the hardening posture below stands.
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
# ADR-0005 — Rust for the image-codec leaf decoders; C++ throughout elsewhere
|
||||||
|
|
||||||
|
* **Status**: Accepted
|
||||||
|
* **Date**: 2026-07-25
|
||||||
|
* **Plan reference**: §7.2, §15, §2.4
|
||||||
|
* **Supersedes**: the deferred-decision note in ADR-0003 (which is now resolved).
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
§15 lists this as an open decision with a hard deadline: "End of M0. Decide
|
||||||
|
once; do not revisit at M4." The decision must be made before M2 begins,
|
||||||
|
because the L1/L2 leaf decoders (filters, image codec glue, CMap parsing)
|
||||||
|
are written in M2, and retrofitting a second language mid-project is worse
|
||||||
|
than either choice made early (§7.2).
|
||||||
|
|
||||||
|
The threat model (§7.1) identifies parser memory corruption as the main risk.
|
||||||
|
The leaf decoders — the code that actually decompresses and decodes the bytes
|
||||||
|
of a hostile PDF's streams and images — are where the historical CVEs live.
|
||||||
|
The question is whether the memory-safety benefit of writing *those* leaves in
|
||||||
|
Rust (behind a C ABI) justifies the build-friction cost of a second language in
|
||||||
|
the tree.
|
||||||
|
|
||||||
|
ADR-0003 recorded the posture that holds regardless of this decision
|
||||||
|
(hardening on in release, bounded views, sanitizers in CI, hardened allocator,
|
||||||
|
resource budgets). This ADR records the language choice.
|
||||||
|
|
||||||
|
## Evidence gathered at M0
|
||||||
|
|
||||||
|
### Leaf-decoder CVE history (the §15 "CVE history" input)
|
||||||
|
|
||||||
|
Surveyed via the CVE record database (2026-07-25):
|
||||||
|
|
||||||
|
* **OpenJPEG** (the JPX/JPEG2000 decoder): **~40 buffer-overflow CVEs**, spanning
|
||||||
|
2016 → 2024, including `CVE-2024-56827` and `CVE-2024-56826` (heap buffer
|
||||||
|
overflow, Red Hat CNAs, recent). Several Chrome CVEs are explicitly
|
||||||
|
"OpenJPEG, as used in PDFium" (`CVE-2016-5157`, `CVE-2016-5158`,
|
||||||
|
`CVE-2016-5159`) — the *exact* leaf decoder the plan bundles (§2.4:
|
||||||
|
"JBIG2 and JPX come from PDFium's bundled decoders"). This is a high-CVE,
|
||||||
|
still-actively-found-bug surface.
|
||||||
|
* **libpng / zlib-ng / libjpeg-turbo**: comparatively mature, few recent
|
||||||
|
memory-safety CVEs (libpng: 2 CVEs in the survey, both old; zlib-ng is a
|
||||||
|
hardening fork of zlib which itself has had ~no memory-safety CVEs in years).
|
||||||
|
* **CMap parsing / PDF object parsing**: smaller surface, but it is *our* new
|
||||||
|
code (no upstream CVE history to lean on) — and it is the most reachable
|
||||||
|
surface from a hostile PDF.
|
||||||
|
|
||||||
|
### Build-friction cost (measured empirically, not estimated)
|
||||||
|
|
||||||
|
`spike/F_rust_ffi_probe/` builds a Rust leaf decoder as a `staticlib` behind a
|
||||||
|
C ABI and links it into a C++ driver:
|
||||||
|
|
||||||
|
* Rust 1.85 `cargo build --release` produces `libfpe_rust_leaf.a` in **~10.5 s**.
|
||||||
|
* The C++ driver links the `.a` by absolute path; the FFI is one `extern "C"`
|
||||||
|
function with bounded (ptr, len) arguments matching the §7.2 bounded-view
|
||||||
|
rule.
|
||||||
|
* The round-trip (C++ → Rust → C++) is **clean under ASan+UBSan** — the FFI
|
||||||
|
boundary is a normal memory-safe-by-construction interface.
|
||||||
|
* The CMake integration is one `add_custom_command` invoking `cargo` plus a
|
||||||
|
`target_link_libraries` with the `.a` path. Corrosion/FetchContent is
|
||||||
|
*not* required for a staticlib leaf.
|
||||||
|
* `panic = "abort"` + `overflow-checks = true` in the release profile mirrors
|
||||||
|
the C++ side's `-fstack-protector-strong` / overflow checks.
|
||||||
|
|
||||||
|
The measured friction is **a `cargo build` step and a `.a` link** — real, but
|
||||||
|
not the multi-day toolchain fight a full Rust integration used to be. The
|
||||||
|
decision is not blocked by build cost.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
**Rust for the image-codec leaf decoders; C++ throughout everywhere else.**
|
||||||
|
|
||||||
|
Concretely:
|
||||||
|
|
||||||
|
1. **In Rust** (built as `staticlib`, behind a C ABI, linked from C++):
|
||||||
|
the **image codec glue** that wraps OpenJPEG, libjpeg-turbo, libpng,
|
||||||
|
OpenJPEG/JBIG2 — the leaf where the CVE history is concentrated. The Rust
|
||||||
|
layer owns the byte-buffer sizing, the allocation, and the call into the C
|
||||||
|
codec; the C codec's output is bounded-checked on the Rust side before it
|
||||||
|
crosses back. This is the narrowest place a second language buys the most:
|
||||||
|
it sits between hostile input and the C decoders that have the CVEs.
|
||||||
|
|
||||||
|
2. **In C++** (the rest of the tree, unchanged): the L1 file layer, the L2
|
||||||
|
object model, the L3 content model, the L4 layout engine, the L5 semantic
|
||||||
|
model, the L6 command layer, the UI, the rendering, the sandbox, the IPC.
|
||||||
|
No second language enters these. CMap parsing and the PDF object parser
|
||||||
|
stay in C++ with the ADR-0003 hardening (bounded views, sanitizers, checked
|
||||||
|
arithmetic) — they are our new code with no upstream CVE history, and the
|
||||||
|
bounded-view discipline plus fuzzing (§8.2) is the right posture for them.
|
||||||
|
|
||||||
|
3. **The image codec glue is the *only* Rust in the tree.** No Rust in the UI,
|
||||||
|
no Rust in the rendering pipeline, no Rust in the model. The build stays
|
||||||
|
single-language for everyone who isn't touching the codec glue.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
**Positive.**
|
||||||
|
|
||||||
|
* The highest-CVE leaf (the image decoders, especially OpenJPEG/JPX where the
|
||||||
|
40-CVE history lives and where "as used in PDFium" appears) gets a
|
||||||
|
memory-safe wrapper. A buffer overflow in the C codec is caught at the
|
||||||
|
Rust boundary's bounds check rather than corrupting the document process
|
||||||
|
heap. This is a real reduction in the §7.1 "parser memory corruption" risk
|
||||||
|
for exactly the surface that has historically been most exploited.
|
||||||
|
* The decision is narrow and defensible: a second language only where its
|
||||||
|
benefit (memory safety for the highest-CVE leaf) clearly exceeds its cost.
|
||||||
|
The rest of the tree pays no Rust tax.
|
||||||
|
* The build-friction cost was measured, not guessed: ~10 s for a `cargo
|
||||||
|
build` of a leaf crate, one `.a` link. CI gets one more build step; local
|
||||||
|
builds for engineers not touching the codec glue are unaffected (the Rust
|
||||||
|
staticlib is a build dependency like any other).
|
||||||
|
|
||||||
|
**Negative.**
|
||||||
|
|
||||||
|
* A second language enters the tree. CI needs `cargo` on the runners (the
|
||||||
|
Gitea Actions matrix already runs on Debian where `apt install rustc cargo`
|
||||||
|
is one line; the nightly packaging pipeline must bundle the Rust staticlib
|
||||||
|
into the artifacts). Contributor friction for the small number of people
|
||||||
|
who touch the codec glue: they need both toolchains.
|
||||||
|
* Debugging across the FFI boundary is harder than debugging within one
|
||||||
|
language — stack traces cross the boundary, and a `panic = "abort"` Rust
|
||||||
|
leaf crashes the process without unwinding. The §7.2 sanitizers + Crashpad
|
||||||
|
still work; the crash is just louder. This is acceptable for a leaf that
|
||||||
|
should never panic in practice (it does bounded byte work).
|
||||||
|
* The Rust leaf must be kept *leaf*: if it grows a dependency on the C++
|
||||||
|
object model it stops being a leaf and the boundary erodes. CODEOWNERS and
|
||||||
|
review enforce this; the C ABI is the contract.
|
||||||
|
|
||||||
|
**Neutral.**
|
||||||
|
|
||||||
|
* `panic = "abort"` matches the C++ side's no-unwinding-across-FFI stance.
|
||||||
|
* The bounded-view (ptr, len) FFI shape is the same shape the §7.2 rule
|
||||||
|
mandates for C++ parsing, so the two sides agree on the discipline.
|
||||||
|
|
||||||
|
## Alternatives considered
|
||||||
|
|
||||||
|
* **C++ throughout (no Rust).** Rejected. The OpenJPEG CVE history
|
||||||
|
(~40 buffer overflows, including 2024 CVEs, several "as used in PDFium") is
|
||||||
|
exactly the surface the §7.1 threat model worries about, and it is the
|
||||||
|
surface where C++ has demonstrably, repeatedly failed. The measured
|
||||||
|
build-friction cost (~10 s `cargo build`, one `.a` link) does not justify
|
||||||
|
forgoing the memory-safe wrapper for that specific leaf. The §15 instruction
|
||||||
|
to "decide once" means the C++-throughout choice would also be permanent;
|
||||||
|
accepting 40 more years of OpenJPEG-class CVEs in our highest-reach surface
|
||||||
|
is the wrong permanent choice.
|
||||||
|
|
||||||
|
* **Rust for all of L1/L2 (the whole parser surface).** Rejected. CMap
|
||||||
|
parsing and the object parser are *our* new code with no upstream CVE
|
||||||
|
history; the ADR-0003 hardening posture (bounded views, sanitizers,
|
||||||
|
resource budgets, fuzzing) is the right tool for new C++ code, and rewriting
|
||||||
|
it all in Rust would spread the second language across the most-coupled
|
||||||
|
part of the tree (the object model touches everything). The marginal
|
||||||
|
safety benefit over hardened+sanitized+fuzzed new C++ does not justify the
|
||||||
|
coupling cost. The leaf is where the benefit concentrates; this decision
|
||||||
|
keeps Rust there.
|
||||||
|
|
||||||
|
* **Rust for the whole document process.** Rejected strongly; Qt, Skia,
|
||||||
|
PDFium, HarfBuzz, FreeType, ICU, OpenSSL are all C/C++. A Rust document
|
||||||
|
process would FFI into all of them anyway, gaining nothing over a C++
|
||||||
|
process with a Rust leaf, and losing the Qt/Skia integration the UI needs.
|
||||||
|
|
||||||
|
## Reproducing the evidence
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build-friction probe:
|
||||||
|
cmake --build build/manual --target spike_f_rust_ffi
|
||||||
|
build/manual/bin/spike_f_rust_ffi 1024 # round-trips 1024 bytes via Rust C ABI
|
||||||
|
|
||||||
|
# CVE history (surveyed 2026-07-25):
|
||||||
|
# OpenJPEG: https://www.cve.org/CVERecord/SearchResults?query=openjpeg+buffer+overflow (~40)
|
||||||
|
# libpng: https://www.cve.org/CVERecord/SearchResults?query=libpng+memory+corruption (2)
|
||||||
|
```
|
||||||
|
|
@ -27,4 +27,7 @@ Per §13.4 and §14 step 9, the following ADRs exist from M0:
|
||||||
* [ADR-0003 — Memory safety posture for the parser layers](0003-memory-safety-posture.md)
|
* [ADR-0003 — Memory safety posture for the parser layers](0003-memory-safety-posture.md)
|
||||||
(§7.2)
|
(§7.2)
|
||||||
* [ADR-0004 — Two-process model with the IPC as a bidirectional trust
|
* [ADR-0004 — Two-process model with the IPC as a bidirectional trust
|
||||||
boundary](0004-two-process-trust-boundary.md) (§2.1)
|
boundary](0004-two-process-trust-boundary.md) (§2.1)
|
||||||
|
* [ADR-0005 — Rust for the image-codec leaf decoders; C++ throughout
|
||||||
|
elsewhere](0005-rust-leaf-decoders.md) (§7.2, §15) — resolves the M0
|
||||||
|
Rust-vs-C++ decision deferred in ADR-0003.
|
||||||
|
|
@ -169,4 +169,10 @@ else()
|
||||||
message(STATUS
|
message(STATUS
|
||||||
"libseccomp not found — Spike E (sandbox) will not be built. "
|
"libseccomp not found — Spike E (sandbox) will not be built. "
|
||||||
"Install libseccomp-dev to enable it.")
|
"Install libseccomp-dev to enable it.")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# --- Spike F: Rust-vs-C++ build-friction probe (ADR-0005 input, §15) ---
|
||||||
|
# Not a product spike; provides the empirical build-cost data for the
|
||||||
|
# Rust-vs-C++ leaf-decoder decision. Has its own CMakeLists so it can be
|
||||||
|
# excluded from builds without Rust.
|
||||||
|
include(${CMAKE_CURRENT_SOURCE_DIR}/F_rust_ffi_probe/CMakeLists.txt)
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
# SPDX-FileCopyrightText: 2025 ai-ad4 and the FreePDFEditor contributors
|
||||||
|
#
|
||||||
|
# Spike F — ADR-0005 Rust-vs-C++ build-friction probe (§15 decision data).
|
||||||
|
# Builds a tiny Rust leaf decoder (staticlib) behind a C ABI and links it into
|
||||||
|
# a C++ driver, to measure the actual integration cost of adding Rust to the
|
||||||
|
# tree. This is the empirical input to the Rust-vs-C++ decision; it is not a
|
||||||
|
# product spike.
|
||||||
|
|
||||||
|
# Find cargo. The probe only builds if Rust is available; the ADR records the
|
||||||
|
# decision either way, so a missing Rust toolchain doesn't block the decision.
|
||||||
|
find_program(CARGO_EXECUTABLE NAMES cargo)
|
||||||
|
|
||||||
|
if(CARGO_EXECUTABLE)
|
||||||
|
set(_rust_dir ${CMAKE_CURRENT_SOURCE_DIR}/F_rust_ffi_probe/rust)
|
||||||
|
set(_rust_lib ${_rust_dir}/target/release/libfpe_rust_leaf.a)
|
||||||
|
|
||||||
|
# Build the Rust staticlib with cargo. The staticlib is the link shape that
|
||||||
|
# avoids a runtime .so dependency — the standard Rust-into-C++ integration.
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${_rust_lib}
|
||||||
|
COMMAND ${CARGO_EXECUTABLE} build --release --manifest-path ${_rust_dir}/Cargo.toml
|
||||||
|
DEPENDS ${_rust_dir}/Cargo.toml ${_rust_dir}/src/lib.rs
|
||||||
|
COMMENT "Building Rust leaf decoder (staticlib) for FFI probe"
|
||||||
|
VERBATIM)
|
||||||
|
|
||||||
|
add_executable(spike_f_rust_ffi
|
||||||
|
common/SpikeRunner.cpp
|
||||||
|
common/SpikeRunner.h
|
||||||
|
F_rust_ffi_probe/cpp_driver.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(spike_f_rust_ffi PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
target_compile_features(spike_f_rust_ffi PRIVATE cxx_std_20)
|
||||||
|
freepdfeditor_apply_warnings(spike_f_rust_ffi)
|
||||||
|
freepdfeditor_apply_hardening(spike_f_rust_ffi)
|
||||||
|
|
||||||
|
# Link the Rust staticlib by absolute path (a .a is an archive, not a
|
||||||
|
# -l flag). Mark it as a dependency of the custom command output so the
|
||||||
|
# Rust build runs before the link.
|
||||||
|
add_dependencies(spike_f_rust_ffi fpe_rust_leaf_staticlib)
|
||||||
|
target_link_libraries(spike_f_rust_ffi PRIVATE
|
||||||
|
${_rust_lib} pthread dl)
|
||||||
|
target_link_options(spike_f_rust_ffi PRIVATE -static-libgcc)
|
||||||
|
|
||||||
|
# Make the staticlib a real CMake target with a custom command so the
|
||||||
|
# build graph knows it is produced, not pre-existing.
|
||||||
|
add_custom_target(fpe_rust_leaf_staticlib DEPENDS ${_rust_lib})
|
||||||
|
|
||||||
|
# Skip the rust spike from the default 'all' target if Rust isn't wanted in
|
||||||
|
# a given build (e.g. a CI image without cargo) — it's gated on CARGO_EXECUTABLE.
|
||||||
|
else()
|
||||||
|
message(STATUS "cargo not found — Spike F (Rust FFI probe) skipped; "
|
||||||
|
"ADR-0005 will record the decision without the empirical build data.")
|
||||||
|
endif()
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// SPDX-FileCopyrightText: 2025 ai-ad4 and the FreePDFEditor contributors
|
||||||
|
//
|
||||||
|
// spike/F_rust_ffi_probe — ADR-0005 build-friction probe. Measures the cost of
|
||||||
|
// adding a Rust leaf decoder to the tree: a tiny `flate`-style decoder stub
|
||||||
|
// (echoes a byte buffer) compiled as a `cdylib`/`staticlib` behind a C ABI,
|
||||||
|
// linked into a C++ driver. The point is the build friction and FFI shape,
|
||||||
|
// not real decompression.
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// The C ABI the Rust crate exposes. Declared here (not from a generated
|
||||||
|
// header) to show the minimal glue shape.
|
||||||
|
extern "C" {
|
||||||
|
// A bounded view, matching the §7.2 "std::span-like bounded views" rule.
|
||||||
|
// The Rust side gets a pointer + length and must not read past length.
|
||||||
|
int fpe_rust_decode(const unsigned char* in, std::size_t in_len,
|
||||||
|
unsigned char* out, std::size_t out_cap,
|
||||||
|
std::size_t* out_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
const std::size_t n = (argc >= 2) ? std::size_t(std::atoll(argv[1])) : 1024;
|
||||||
|
std::vector<unsigned char> input(n, 0x5A);
|
||||||
|
std::vector<unsigned char> output(n * 2, 0);
|
||||||
|
|
||||||
|
std::size_t out_len = 0;
|
||||||
|
int rc = fpe_rust_decode(input.data(), input.size(),
|
||||||
|
output.data(), output.size(), &out_len);
|
||||||
|
if (rc != 0) {
|
||||||
|
std::fprintf(stderr, "decode failed: %d\n", rc);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// Verify the (stub) decoder echoed the input.
|
||||||
|
if (out_len != input.size() ||
|
||||||
|
std::memcmp(output.data(), input.data(), input.size()) != 0) {
|
||||||
|
std::fprintf(stderr, "decode output mismatch\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::printf("rust_ffi_probe: decoded %zu bytes via Rust C ABI, rc=%d\n",
|
||||||
|
out_len, rc);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fpe_rust_leaf"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
# SPDX-FileCopyrightText: 2025 ai-ad4 and the FreePDFEditor contributors
|
||||||
|
#
|
||||||
|
# ADR-0005 build-friction probe crate. A staticlib so the C++ driver links it
|
||||||
|
# without a runtime .so dependency. crate-type = ["staticlib"] is the standard
|
||||||
|
# Rust-into-C++ integration shape; "cdylib" would be for a .so.
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "fpe_rust_leaf"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "GPL-3.0-or-later"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "fpe_rust_leaf"
|
||||||
|
crate-type = ["staticlib"]
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
# Release profile: the leaf decoder ships hardened. Mirrors the C++ side's
|
||||||
|
# -fstack-protector-strong / overflow checks via Rust's default release
|
||||||
|
# arithmetic checks and overflow guards.
|
||||||
|
[profile.release]
|
||||||
|
panic = "abort" # match the C++ side: no unwinding across the FFI
|
||||||
|
overflow-checks = true
|
||||||
|
lto = true
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// SPDX-FileCopyrightText: 2025 ai-ad4 and the FreePDFEditor contributors
|
||||||
|
//
|
||||||
|
// ADR-0005 build-friction probe: a Rust leaf decoder behind a C ABI. The
|
||||||
|
// crate builds as a staticlib so it links into the C++ driver without a
|
||||||
|
// runtime .so dependency. The "decode" is a stub (echo the input) — the
|
||||||
|
// point is the FFI shape and build cost, not real decompression.
|
||||||
|
|
||||||
|
use std::slice;
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
/// `in_buf` must be valid for `in_len` bytes; `out_buf` for `out_cap`.
|
||||||
|
/// Returns 0 on success and writes the decoded length to `*out_len`.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn fpe_rust_decode(
|
||||||
|
in_buf: *const u8,
|
||||||
|
in_len: usize,
|
||||||
|
out_buf: *mut u8,
|
||||||
|
out_cap: usize,
|
||||||
|
out_len: *mut usize,
|
||||||
|
) -> i32 {
|
||||||
|
// Bounded view — the §7.2 rule, enforced by the type system here.
|
||||||
|
if in_buf.is_null() || out_buf.is_null() || out_len.is_null() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if in_len > out_cap {
|
||||||
|
return 2; // output buffer too small
|
||||||
|
}
|
||||||
|
let input = unsafe { slice::from_raw_parts(in_buf, in_len) };
|
||||||
|
let output = unsafe { slice::from_raw_parts_mut(out_buf, out_cap) };
|
||||||
|
// The "decode": echo. Real Flate/LZW/CMap decode goes here in M2.
|
||||||
|
output[..in_len].copy_from_slice(input);
|
||||||
|
unsafe { ptr::write(out_len, in_len) };
|
||||||
|
0
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue