Skip to content

Store EBML element defaults in the schema and resolve zero-length elements - #86

Merged
kinetiknz merged 6 commits into
masterfrom
schema-defaults
Aug 26, 2026
Merged

Store EBML element defaults in the schema and resolve zero-length elements#86
kinetiknz merged 6 commits into
masterfrom
schema-defaults

Conversation

@kinetiknz

Copy link
Copy Markdown
Collaborator

Centralizes per-element default values in the descriptor tables so absent elements read back as their spec default, replacing the ad-hoc default constants that were scattered across the accessor call sites (and dropping the presets those left dead).

Builds on the merged zero-length change (1c9936b): per RFC 8794 §6.1 an empty element with a declared default must resolve to that default rather than 0, which fixes cases like a zero-length TimestampScale that previously read as 0 and broke duration and block-duration scaling.

Behavior is unchanged for well-formed files (the regress suite is byte-identical), and zerolen_default.webm is added to cover the empty-defaulted-element case the existing suite missed.

@kinetiknz
kinetiknz requested a review from ashleyz July 6, 2026 04:46
@kinetiknz kinetiknz self-assigned this Jul 6, 2026
Comment thread src/nestegg.c
Comment thread src/nestegg.c
Comment thread src/nestegg.c
Declare each element's spec default in the descriptor tables and copy it into
storage during parsing, so absent elements read back as their default instead
of via ad-hoc constants scattered across the accessor call sites. The read
flag becomes a tri-state so a value taken from the file is distinguishable
from a default. Defaults a static table can't express (DocType, dynamic
DisplayWidth/Height, BitDepth) stay at their call sites.
For fields with a schema default, ne_get_uint/ne_get_float always write the
destination, so the "value = N" presets in the parameter accessors were dead
stores, and misleading where the real default was non-zero (e.g. 2 for
MatrixCoefficients). Replace them with a guard on the return value, assigning
only on success; params is zeroed up front so the impossible unread path
degrades to zero. Presets encoding a real fallback (BitDepth 16, dynamic
DisplayWidth/Height, the mastering NaN sentinels) are kept.
Since the upstream zero-length change (1c9936b) a zero-length numeric encoding
reads as 0, but an empty element with a declared default must resolve to that
default, so a zero-length TimestampScale wrongly read as 0 and broke duration
and block-duration scaling. In ne_read_simple, keep the value already
installed at init (the default, or zero when none is declared) instead of
reading a zero-length element. Add zerolen_default.webm, which the suite
missed because bug2045549.webm only covers a zero-length cluster Timestamp on
the direct read path.
zerolen_default.webm only exercises a zero-length element with a declared
uint default (TimestampScale), so the float path had no coverage.  Add
zerolen_audio.webm, holding a zero-length SamplingFrequency, which resolves
to the declared default of 8000.0, and a zero-length BitDepth, which reads
back as 0 rather than the 16 reported for an absent BitDepth.  The next
commit fixes the BitDepth case and updates the expectation.
Comment thread src/nestegg.c Outdated
DisplayWidth, DisplayHeight and BitDepth carry no default in the element
descriptors: the first two derive theirs from PixelWidth and PixelHeight, and
BitDepth has no declared default at all, with 16 supplied as nestegg API
policy.  A zero-length instance of any of them was therefore read as a value
of 0, while an absent one picks up the accessor's default, so an empty
DisplayWidth reported a display width of 0 instead of the pixel width.

RFC 8794 Section 6.1 defines the value of an Empty Element only for elements
with a declared default (the default) and without one (zero); these have
neither, and RFC 9559 declares all three range="not 0", so zero cannot be
what the file meant.  Mark them DESC_FLAG_ACCESSOR_DEFAULT and record a
zero-length instance as read but valueless, so the accessor resolves it
exactly as it resolves an absent element.  libavformat's matroskadec reaches
the same result by resolving an empty element to its syntax table default,
where DisplayWidth and DisplayHeight hold the sentinel it uses for absent.

Read but valueless is a state of its own, EBML_READ_EMPTY, because the two
users of the read state want different answers for it.  The parser skips
repeated instances of an element it has already read, so an empty instance
has to count as read or a later instance would override it, breaking the
first instance wins rule that applies everywhere else.  The accessors, on
the other hand, must see it as absent to supply the default.  Neither can use
EBML_READ_DEFAULT, which is established before parsing and so has to leave
the element open to being read.

Add zerolen_display.webm and zerolen_repeat.webm, the latter covering a
DisplayWidth repeated after an empty instance and before one, and update the
zerolen_audio.webm expectation.
BlockAdditions are parsed outside the element descriptors, and that manual
parse overwrote the preset default with the zero ne_read_uint returns for a
zero-length element, which the following add_id == 0 check then rejected: a
zero-length BlockAddID failed the whole packet read rather than resolving to
1, the default RFC 9559 Section 5.1.3.5.2.3 declares for it.  Read the value
only when the element is not empty.  libavformat's matroskadec defaults the
same element to 1.  Add zerolen_blockaddid.webm.

@ashleyz ashleyz left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@kinetiknz

Copy link
Copy Markdown
Collaborator Author

Thanks for the review!

@kinetiknz
kinetiknz merged commit d6ec556 into master Aug 26, 2026
2 checks passed
@kinetiknz
kinetiknz deleted the schema-defaults branch August 26, 2026 20:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants