mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-13 22:12:12 +00:00
* media: bound Matroska resync work and header reads What was wrong: the Matroska demuxer did no bounded work when recovering from damage. `resync` tested every plausible-looking element ID, and each test could refill the 64 KiB read window at a far offset, so a damaged region cost about 13,000x read amplification (a 1 MB crafted file read 4.3 GB). The EBML header was read with no size cap, so a header as large as the file read the whole file into memory. The warnings list grew without limit, and block and lace sizes were narrowed to u32 with `as`, so a block over 4 GiB was indexed with a truncated size. What changed: damage recovery now has a 64 MiB per-file budget, charged by the bytes actually read to refill the window; the scan stops when it is spent and a warning says so. The EBML header is limited to 4 KiB (real ones are a few dozen bytes). At most 256 warnings are kept, plus one saying the rest were dropped. Block and lace sizes use checked conversion; a block that does not fit is skipped like other damaged blocks. How it was tested: four new tests in synthetic_tests.rs failed before the fix and pass after it: a counting ByteSource over dense fake Clusters (reads stay within the budget), an EBML header as large as the file (quick NotMatroska error, small reads), a SeekHead with thousands of bad entries (warnings capped), and a sparse source with a block over 4 GiB (not indexed). The rest of the matroska tests still pass, and clippy is clean with -D warnings. This carries over the intent of storytold/filmcraft#2951 (a recovery-work budget for Matroska) now that the demuxer lives in craft-libs, and adds the header, warning and size limits from the audit. Completes the Matroska part of storytold/filmcraft#2794 * media: cap laced Matroska block reads and give each index pass its own recovery budget Review follow-ups to the Matroska recovery budget. A laced block's whole body was read into memory before any size check, so a block declaring 4 GiB and carrying a lacing flag allocated that much. Laced blocks hold small frames, so a body over 16 MiB is now skipped like other damaged blocks, without reading it. The recovery budget lived in the read cache, which open and build_index share, so open(index: false) followed by build_index() charged the same damaged region twice and could drop later clusters. Each pass over the file (open_impl) now starts with the whole budget, and packet reading starts with its own. The warnings test now pins the exact count (256 kept plus the note) and the note's text. Tested: a sparse file with each lacing flag fails to open before and opens with no samples after; a file whose pre-cluster damage costs about 50 MiB of recovery reads loses its cluster in the second pass before and keeps it after. The matroska unit tests pass, clippy -D warnings and fmt are clean. * media: restore the packet-reading recovery budget after build_index build_index runs a full pass over the file on the shared read cache, so it spent the recovery budget that next_packet then relied on. After open(index: false) and build_index(), reading stopped at a damaged region the index had recovered across, ending playback before the next cluster. build_index now gives packet reading a whole budget again once its own pass is done. Tested: a file with a cluster, about 40 MiB of recovery reads of damage, then a second cluster; after open(index: false) and build_index() the index held both clusters but reading ended after the first before the change, and returns both after it.