mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-26 02:58:32 -06:00
47d2a8f16ed8a1dd6714734828a4528a97ccc4e1
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9526363152 |
Build each crate's integration tests as one binary (#4087)
* Build each crate's integration tests as one binary Every tests/*.rs file is its own executable that links the whole crate graph. Move each crate's test files into tests/it/ as modules of one tests/it/main.rs, for apps/pdfcraft, apps/pdfcraft-cli, automation, cos, crypt, engine, render, sign and ui-egui. ui-egui's ui_group binary and the support modules move into tests/it/ too. Crates with a single test file keep it. The test executables go from 150 to 52, and every crate runs the same number of tests as before. ui-egui perf keeps its own binary (own process): its frame-time budgets would charge each measurement for the other tests running beside it. What the move needs: - include_str!/include_bytes! and #[path] paths gain one "../". - ui-egui: one GPU lock in tests/it/main.rs replaces the five per-file ones (content, document_links, editing, fill_sign, view), so one process creates one wgpu device at a time. The drop-line test in ui.rs and the screenshot blocks behind PDFCRAFT_*_SHOT(S) and CRAFT_UI_DOCKING_DIR, which render without holding it, take it too. - pdfcraft winit_wayland_scroll: the vendored winit source it compiles names crate::event, so that shim lives in main.rs. - parity evidence names the new paths. * Name the one-binary test paths in docs and comments - READMEs, docs, ATTRIBUTION and source comments name tests/it/<file>.rs. - The oracle README's example filters with --test it encryption::qpdf_agrees. - pdfcraft's GPU lock comment points at the UI tests' shared lock in crates/ui-egui/tests/it/main.rs. |
||
|
|
ecaa2980ba |
crypt: say how the quoted vectors reproduce, and name what nothing verifies
FredApps' approval on #2359 carried one non-blocking nit that is a real defect: the header told
contributors to "regenerate with make-oracle-fixtures.py in the workspace root" - a script that
exists only in my checkout, next to a venv (.dc-tools/) likewise outside the repository. A
contributor following it finds nothing, and this repository has no committed generator for these fixtures
(crates/oracle treats qpdf strictly as an external process, and CLAUDE.md keeps the product and build in
Rust).
The header now carries a self-contained recipe, measured on this host rather than asserted:
* one blank 200x200 page, pikepdf 9.11 / qpdf core 12.2.0, Encryption(owner=OWNER,
user=USER, allow=Permissions(extract=False), R=<revision>, aes=…, metadata=…),
save(static_id=True), then read /Encrypt. At revisions 2-3 `aes=False, metadata=False` are
FORCED - pikepdf refuses AES ("Cannot encrypt with AES when R < 4") and refuses encrypted
metadata ("Cannot encrypt metadata when R < 4") - so leaving those flags to the reader makes
the export-grade row unreproducible. My first draft of this comment made exactly that mistake.
* Reproduction scope, by revision: each of the four deterministic rows quotes eight dictionary
fields (32 per comparison leg). Revisions 2 and 3 reproduce as whole files - fresh writes today
carry the same sha256 (63f2d95b…, 39afbdd5…) as the October originals these literals came
from. Revision 4 reproduces all eight fields, but its file moves by exactly one ~32-byte
stream because /CFM /AESV2 prepends a fresh random IV (ISO 32000-2 §7.6.4); the dictionary
and /ID are unchanged. Revision 6 reproduces structurally only (/ID, R/V/Length/P and the
metadata flag); its salts are new each run.
Running the generator at the reviewer's prompting exposed that it had not been executed since its
last edit: unless both aes=False and metadata=False are forced pikepdf refuses R<4, checking metadata first, so it wrote 4 of the 6 fixtures - revisions 2
and 3 missing, including this file's headline export-grade row - while printing a "skipped:" line
and exiting 0 regardless. It writes 6 of 6 now, and every quoted field reproduces as above.
The overstatements flagged during review are corrected in this file; they are quoted verbatim,
each with the basis for its new wording, in the PR description rather than here - commit messages
cannot be edited after a push, descriptions can. Two further fixes live in this message alone: an
ISO subsection written as §7.6.4.3, a form this repository does not cite (it uses bare §7.6.4 or the
deeper §7.6.4.x.y), and one clause duplicated mid-cycle.
What nothing here verifies, stated instead of implied - established by deliberately breaking the
table (4 mutations: 2 red, 2 green): authentication never reads /Perms (a corrupted one fails
only this file's own decrypt), and renaming a row's /StmF to Identity or replacing its
crypt-filter set leaves all three tests green, so the filter names are transcription held to
review. Pinning them, and re-deriving these rows through pdfcraft_oracle::qpdf() on the runner
that installs qpdf, belong in a follow-up. Also recorded honestly: the revision-6 fixtures these
rows were copied from are gone - my own regeneration overwrote them before I archived the folder -
so those two rows rest on internal consistency plus this record, not a surviving artifact. Revisions 2 and 3 carry their own proof, byte for byte (sha256 63f2d95b… and
39afbdd5…, October and today); revision 4 reproduces every quoted field, with only the one
IV-carrying stream moving.
Scope: comments plus one assertion message (a revision 3 /U description that wrongly claimed qpdf
leaves sixteen zero bytes; its condition, len == 64, is unchanged). No assertion logic, fixture,
dependency or product code touched. Parent remains the approved
|
||
|
|
cdbce8ac17 |
crypt: authenticate against encryption numbers we did not compute
Adds crates/crypt/tests/qpdf_literals.rs (305 lines): three tests that hand our security
handler /O, /U, /OE, /UE and /Perms values from outside implementations, then require our
reader to authenticate them and report the intended role. Tests only - no product code, no
new dependency; PDFs stay outside the repository and only quoted hex is committed.
Why outsiders wrote these numbers: create() and open() share one derivation, so a
wrong-but-self-consistent implementation passes every internal test. Two review rounds proved
it by mutation - several key-derivation breaks stayed green in both suites because writer and
reader drifted together. Quoting bytes we did not compute breaks the circle: open() recomputes
/U (or the revision 6 hash chain) and compares it to what was stored, so a divergent derivation
cannot authenticate.
Six cases come from qpdf core 12.2.0 via pikepdf (R2/R3, R4 in both metadata modes, R6 in both).
Revisions 2-4 are deterministic given /ID and passwords, so the generator reproduces their quoted bytes
byte for byte (verified). Revision 6 salts are fresh on every run by design: these literals match the
fixtures as captured, and re-running the generator will produce different - equally valid - revision 6
numbers, so compare R/V/Length structurally there and re-quote if you regenerate. One
case is HAND-DERIVED, labelled as such in the source: an independent re-implementation of ISO
Algorithms 2/3 at n = 5, cross-validated by having that same routine reproduce qpdf's n=16 /O
byte for byte. It exists because no conforming producer emits R3 at 40 bits - qpdf writes 128
there and this crate's create() maps Rc4_40 to revision 2 - yet the derivation loop only runs
for r >= 3, so without a crafted row its truncation is unobservable and mutating it changes
behaviour that nothing catches.
Measured effect, same battery, upstream --lib run separately each time:
r6-hash-selector (%3 collapsed to %2) CAUGHT here; upstream stays green
r6-sha-arms-swapped (SHA-384 <-> SHA-512) CAUGHT here; upstream stays green
r6-selector-window (sums 17 bytes not 16) CAUGHT here; upstream stays green
r4-key-chain-truncation (loop hashes whole digest) CAUGHT here, via the crafted row;
upstream stays green. Pristine 3/3 pass, mutated 2 passed / 1 FAILED - verified twice.
Still invisible, named rather than glossed: r6-round-floor (minimum rounds 64 -> 63) - the
terminator does not separate the two floors on these inputs; /OE,/UE length loosening and the
/Perms filler source, both unreached here.
Corrections carried from review: qpdf's advice against a static /ID does not weaken these
revision 6 vectors, because key_r6 never receives the file /ID at all; it matters only to the
revision 2-4 paths, where we quote /ID literally so the numbers stay exact. And no ATTRIBUTION
entry is added, matching crates/crypt/src/tests.rs:237 which already quotes qpdf vectors
unattributed; cargo xtask assets stays ok.
Gates on a clean tree: cargo test -p pdfcraft-crypt 28 passed / 0 failed (17 lib + the 8 properties from #2358, now merged,
plus 3 oracle tests - one
per concern - role authentication across seven dictionaries, metadata-flag key separation, /Perms
decode);
clippy --all-targets -D warnings clean; cargo fmt --check clean; xtask layers OK (35 crates).
Integrity note: the literals in this commit were checked field by field against the PDFs on disk -
27 of 27 fields quoted verbatim, no transcription drift. A regression test for that check belongs in the
generator's output comparison rather than here, since it needs pikepdf.
|
||
|
|
21e7903ff2 |
crypt: property tests for encrypted content, crypt filters and damaged ciphertext (#2358)
Adds crates/crypt/tests/proptests.rs (464 lines): eight properties over the payload and ciphertext side of the standard security handler. Tests only - no product code, no new dependency (proptest was already a dev-dependency here). Upstream's src/tests.rs covers this handler well with fixed vectors (including files qpdf wrote) and one property over malformed dictionaries. Its encrypted round trips run three shapes - a 30-byte text, a 1000-byte ramp and a 10-byte attachment. Not walked before: empty content, block boundaries (15/17), bytes handed to the decoder at lengths it never encrypted, key widths only key_len decides (5/16/16/32), /Perms verified by nothing, and Algorithm 2 step f for unencrypted metadata on revisions below 4. Evidence: 25 one-line breakages of crypt/src, each run against these properties AND upstream --lib; restored and byte-compared per row; verdicts from cargo exit codes. 16 seen here, several by nothing else in the repository (/Num fold width both directions, RC4-40 key length, /EFF selection, Identity name guard, IV scoping, step f, /Perms tail). Named blind spots: four hash_r6 breaks stay green because create() and open() share that function - the companion branch quotes qpdf's own numbers to close them. Gates: cargo test -p pdfcraft-crypt 25 passed / 0 failed; clippy --all-targets -D warnings clean; fmt clean. Default 256 cases, nothing lowered. Co-authored-by: Nick Nyssen <314538108+amoisttowe1@users.noreply.github.com> |