Commit Graph
12 Commits
Author SHA1 Message Date
KenM76andClaude Opus 5.5 bca0211adc M12: PRC reader: charge each triangle's corner arrays, not only its corners
Review fix from #6087. tess_3d charged one element per triangle, but each
triangle also holds its normal-slot corners and one corner triple per
texture set any face of the mesh uses, up to eight. Faces reusing one
index range could so allocate well past the 128 MiB the element ceiling
documents. Each face's triangles are now charged in 8-byte elements for
everything they hold (corners, normal slots, texture triples, style)
before the face is triangulated.

Tests, each shown failing with the fix reverted:
reused_textured_faces_are_charged_for_every_corner_array,
faces_reusing_one_index_range_are_charged_for_each_triangle_they_make.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-04 06:50:26 -05:00
KenM76andClaude Opus 5.5 b4ba4edc7f M12: PRC reader: charge copied blocks, refuse overflowing schema expressions
Review fixes from #6087:

- Picture and uncompressed blocks are charged to the parse ceiling before
  they are copied. File-structure headers may overlap, so each structure's
  copy counts. The ceiling test now includes the 3-byte picture.
- Schema expressions use checked multiply, add and subtract; an overflow is
  Malformed rather than a wrapped loop count.
- Each face's triangles are counted from its entity sizes and charged before
  it is triangulated.
- Matrix::mirrors divides each row by its largest entry before taking the
  determinants, so a mirror whose determinant underflows still reverses the
  winding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-03 23:58:08 -05:00
KenM76andClaude Opus 5.5 d0286a745a M12: PRC reader: winding by the whole determinant; walk charges each schema once
A negative w (uniform or projective) turns mapped triangles inside out
though the 3x3 block does not mirror; the flip and the normal sign now
follow the 4x4 determinant, falling back to the 3x3 block when the last
row is zero.

The tree walk read each structure's globals schema with its own full
allowance, then again for the globals. It now reads it once, charged to
the walk, and continues from there for the globals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-03 22:56:09 -05:00
KenM76andClaude Opus 5.5 b1a6128dbd M12: review fixes from the renderer PR's review of the reader
- Huffman leaf tables where one code begins another are malformed; the
  shorter leaf would end the longer code early and misalign the payload.
- A model-file end past the input is malformed rather than clipped.
- Each schema entry costs one element, so empty programs are not free.
- tessellations() carries the globals schema's remaining allowance into
  the tessellation reader: one stream, one budget.
- A face behaviour wider than 16 bits is malformed, not zeroed.
- Coordinate arrays that are not whole points are malformed; a partial
  last normal drops the stored normals, as an unusable normal slot does.
- Triangles a TESS_3D's faces emit are charged to the element budget as
  they are made, since faces may reuse one index range.
- TriangleMesh::transformed drops stored normals under a projective
  matrix (last row not [0, 0, 0, w]).
- Placement::tessellation's doc says placements() does not check it
  against the tessellation section.

Each fix has a test, seen failing with the fix reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-03 21:36:07 -05:00
KenM76andClaude Opus 5.5 cb317560fe M12: PRC reader: answer review (#1297)
- Schema programs draw on one step allowance per reading pass:
  MAX_SCHEMA_STEPS (2^22) moves into the BitReader's budget next to the
  decoded-element allowance, and the tree walk carries both from reader
  to reader. A per-call ceiling let a cheap entity repeated many times
  run ~2^22 steps each.
- The vertex-colour pre-check charges the minimum encoding (one full
  colour, then one reuse bit each), so a run of reused colours may end a
  section.
- placements() reports unreadable globals when an entity reference, not
  only an item, names a reference coordinate system.
- chunks_exact -> as_chunks for clippy 1.99 (chunks_exact_to_as_chunks).

Tests: programs_on_one_stream_share_the_step_allowance,
one_step_allowance_covers_the_whole_walk,
reused_vertex_colours_may_be_the_last_data,
unreadable_globals_are_an_error_when_a_reference_names_a_coordinate_system;
each fails with its fix reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-02 14:38:13 -05:00
KenM76andClaude Opus 5.5 5eb484c235 M12: PRC reader: a test for each input guard (#1297)
Disabling each input guard in turn left several with no failing test.
Each now has one that pins the guard's own refusal, not a later error:

- a zero Huffman code-length width, and a zero code length;
- a boolean array longer than the data, refused before any bit is read;
- a character array count past the data;
- no file structures, or more than MAX_FILE_STRUCTURES;
- a schema entry count past the data;
- normal angles wider than 16 bits;
- more texture sets than a face may carry (now by message);
- representation sets nested past the ceiling (and at it, accepted);
- a prototype chain past the ceiling;
- the element allowance, at each place it is charged: an entity's
  counted array, uncompressed character and short arrays, a schema
  program, and across one walk's readers (walk_within takes the
  allowance so a test can set it; an allowance that fits one file
  structure does not fit two).

Each fails with its guard disabled (the texture-set one by aborting on
the allocation the guard prevents).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-02 13:52:20 -05:00
KenM76andClaude Opus 5.5 a7cc7586d1 M12: PRC reader: cap decoded array elements per reading pass (#1297)
The inflation cap bounds bytes, but one bit can decode to a 4-8 byte
element, so a 512 MiB section could ask for about 16 GiB. Every array
count is now charged against MAX_DECODED_ELEMENTS (2^24), shared by
all readers of one tree walk; over the allowance is TooManyElements.
Huffman pre-sizing is capped at 1024.

decoded_elements_are_charged_against_one_allowance fails with the
charge in BitReader::take_elements removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-02 13:27:05 -05:00
KenM76andClaude Opus 5.5 c566ca6144 M12: PRC reader: tree markups and views read past (#1297)
CAD 3D files carrying PMI failed model_tree() and placements() with
"tree markups (PMI) is not supported": the tree section has no length
prefix, so MarkupData and views had to be parsed to be skipped.

Now read past, not modelled (new tree/markup.rs):
- MarkupData [WD 7.3.10.4]: markup linked items (204), leaders (503),
  markups (502), annotation items/sets/references (504-506); sets
  nest under the MAX_DEPTH ceiling.
- Views (501) [WD 7.7.3], with their annotation plane (SURF_Plane 86),
  optional scene parameters, and from 8137 their flags, linked items
  and filters.
- Scene lights (731-734) and clipping planes, so
  SceneDisplayParameters no longer refuses them.
ReferenceData and ReferenceOnPRCBase are shared readers now, and skip
their schema-added fields.

Field orders the working draft leaves open follow prc-rs's format
description (MIT); each such reader carries a `prc-rs:` marker.

Tests: one synthetic test per entity kind (each reads to a sentinel
and every truncation errors), nesting ceiling, wrong annotation type,
and a tree with markups and views on a part and an occurrence that
still places its mesh. Each shown failing with its reader reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-02 12:26:44 -05:00
KenM76andClaude Opus 5.5 c571b13469 M12: PRC reader: product tree and styles (#1297)
Walk the PRC product tree into placed, coloured tessellations.

- PrcFile::placements / placements_with(StyleAlpha, EntityOverrides):
  every drawable tessellation reached from the model file's roots, placed
  by its occurrence path's locations and then the representation item's
  local coordinate systems (a 4x4 Matrix) [WD 7.3.10.1, 7.6.3.2].
- Colours: styles and materials from the file structure globals, the
  father/son inheritance bits [WD 7.2.4.2], style transparency combined
  with material alpha by StyleAlpha, per-face styles and topology
  references via Placement::triangle_colours. A prototype's part is
  coloured by its own structure's styles. A colour index counts the colour
  array's doubles (pdf-issues #816).
- Occurrences' entity references: colour, hide or relocate the entity
  they name, in the occurrence's subtree, everywhere, or not at all.
- PrcFile::model_tree: the assembly tree as a model-tree panel lists it,
  with name sources, visibility and each node's placement range.
- TriangleMesh::transformed applies a Matrix: positions, cofactor-mapped
  normals, winding flipped under a mirror.

Never panics: occurrence and prototype cycles are Malformed; depth 64,
2^20 visits, MAX_PLACEMENTS and a face-override ceiling bound the walk;
counts are checked against the remaining data before allocating.
Markups, views, scene lights and clipping planes are Unsupported;
textures are read past.

Field orders the working draft leaves open follow prc-rs (MIT); each is
marked `// prc-rs:`, as are the step-1 container, Double and schema
choices that did.

Adapted from pdfcer (MIT, same author), crates/pdfcer-3d/src/tree and
vec3.rs at 0c9742b5936232af89cd707138ef149dc1f4a660.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-02 12:26:44 -05:00
KenM76andClaude Opus 5.5 7f30266790 M12: PRC reader: tessellation and arrays (#1297)
Read the tessellation section of a PRC file structure:
FileStructure::tessellations() returns each entity as a Tessellation.

- TESS_3D becomes a TriangleMesh: positions, triangles from the
  triangle/fan/strip blocks (one-normal and textured variants included),
  one triangle range per face, stored normals and texture coordinates
  with per-corner indices, and per-triangle line-attribute styles.
- TESS_3D_Wire becomes polylines (closing and continuous flags); vertex
  colours are read past, sized by points (closing point included).
- TESS_Markup is read past.
- TESS_3D_Compressed is read field by field to its end so the entities
  after it decode; its triangles are not rebuilt (later step).
- The compressed Character/Short/Integer/Indice arrays and the Huffman
  container they use (WD 11.6-11.9, 12.2) live in arrays.rs; the base
  fields every entity shares (attributes, name, user data) in entity.rs.

Never-panic hardening beyond the source this is adapted from:
- Huffman symbol width must be 1..=16; the leaf table and element count
  are bounded by the payload before anything is allocated.
- A face may carry at most 8 texture-coordinate sets (an unbounded
  count allocated per set and padded per triangle).
- Every count is checked against the remaining bits; indice sums and
  deltas use checked arithmetic.

A few reading choices (Huffman bit order and code-length width, indice
running sums, 64-bit attribute order, three version gates, the
is_segment_color bit, the compressed field order) follow prc-rs (MIT);
each is marked `// prc-rs:` in the source.

Tests are synthetic, written with the crate's test bit writer. Each
fix was reverted and its test seen failing (17 reversions).

Adapted from pdfcer (MIT, same author), pdfcer-3d at
0c9742b5936232af89cd707138ef149dc1f4a660.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-02 12:26:43 -05:00
KenM76andClaude Opus 5.5 ab552430a7 M12: PRC reader: the Double code table from prc-rs (MIT) (#1297)
Every PRC Double decodes through a fixed 2077-row prefix-code table
(working draft 11.17.2). The rows come from prc-rs src/double.rs at
287b671bb5b31b05efb5ec1b98f7c359b9e4dd7d under its MIT licence, whose
notice stays in the file header; ATTRIBUTION.toml records it.

The table is a complete prefix code, so the empty-table stub test is
replaced by a truncation test. The exponent lookup masks the 11 exponent
bits, because the last exponent row's upper word is 0x7ff80000.

Revert proof: with the table emptied, eight Double tests fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-02 12:26:43 -05:00
KenM76andClaude Opus 5.5 83b230ae73 M12: PRC reader crate: container, bit primitives, schema (#1297)
New standalone L0 crate pdfcraft-prc: the PRC container walk (file
header, file-structure descriptions, per-section zlib inflation under a
512 MiB ceiling), the bit-level primitives of the compressed sections,
and the schema interpreter a newer producer uses to describe fields it
added (step and nesting ceilings).

The Double prefix-code table is not in this commit: Double reads
return UnknownDoubleCode, and the tests that need the table are
ignored with that reason.

Adapted from pdfcer (MIT, same author), crates/pdfcer-3d at
0c9742b5936232af89cd707138ef149dc1f4a660.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-02 12:26:43 -05:00