Skip to content

moq-net: make Group/Frame producers track-only constructible#1636

Merged
kixelated merged 1 commit into
devfrom
claude/amazing-heyrovsky-59675c
Jun 6, 2026
Merged

moq-net: make Group/Frame producers track-only constructible#1636
kixelated merged 1 commit into
devfrom
claude/amazing-heyrovsky-59675c

Conversation

@kixelated
Copy link
Copy Markdown
Collaborator

Summary

GroupProducer::new_with_timescale leaked into the public API. It only existed because GroupProducer/FrameProducer were publicly constructible and a group needs its parent track's timescale to validate frame timestamps. Rather than un-leak that one method, this closes every public construction door so groups/frames can only be built through a track.

  • Collapse GroupProducer::new + new_with_timescale into a single pub(crate) fn new(info, timescale).
  • Make FrameProducer::new and Frame::produce pub(crate); gate the test-only Group::produce behind #[cfg(test)].
  • Remove the unused From<Group> for GroupProducer and From<Frame> for FrameProducer impls.
  • Update the three internal TrackProducer call sites.

Groups now only come from a TrackProducer and frames only from GroupProducer::create_frame, so the timescale is always the track's negotiated value. The append_frame timestamp/timescale check stays a hard error (Error::TimestampMismatch): with construction gated, a scale mismatch (or a missing/extra timestamp) is a genuine producer bug, not a recoverable condition.

Why this shape

  • Kept Timestamp (no u64 in Frame). The self-describing scale is load-bearing: Timestamp::convert(), the zigzag delta encode in the lite publisher's serve_frame, and hang's convert(TIMESCALE) all depend on it. A bare u64 wouldn't remove the wrong-timescale footgun, it would make it silent and uncatchable.
  • No warn/convert in publisher.rs. Once construction is track-gated the group always knows the negotiated timescale, so the per-byte hot path doesn't need to second-guess it. serve_frame's frame.timestamp.expect(...) still rests on the model-layer invariant.
  • Construction-dependence only. This restricts how Group/Frame are constructed, not how they close. They remain independently closeable and shareable, so the no-cascade-abort contract is untouched.

Compatibility

This is a breaking change to rs/moq-net's public Rust API (hence targeting dev), but there is zero external churn: every caller in the workspace already builds via create_group / create_frame, including the lite subscriber receive path. No JS/wire/catalog changes, so no Cross-Package Sync rows apply.

Test plan

  • cargo test -p moq-net --lib (365 passed)
  • cargo build --workspace
  • cargo clippy -p moq-net --all-targets (clean)
  • cargo fmt -p moq-net

🤖 Generated with Claude Code

(Written by Claude)

@kixelated kixelated enabled auto-merge (squash) June 6, 2026 02:22
`GroupProducer::new_with_timescale` leaked into the public API. It only
existed because `GroupProducer`/`FrameProducer` were publicly constructible
and a group needs its parent track's timescale to validate frame timestamps.

Close every public construction door instead of just that one method:

- Collapse `GroupProducer::new` + `new_with_timescale` into a single
  `pub(crate) fn new(info, timescale)`.
- Make `FrameProducer::new` and `Frame::produce` `pub(crate)`; gate the
  test-only `Group::produce` behind `#[cfg(test)]`.
- Remove the unused `From<Group> for GroupProducer` and
  `From<Frame> for FrameProducer` impls.

Groups now only come from a `TrackProducer` and frames only from a
`GroupProducer::create_frame`, so the timescale is always the track's
negotiated value. The `append_frame` timestamp/timescale check stays a hard
error (`Error::TimestampMismatch`): with construction gated, a mismatch is a
genuine producer bug rather than a recoverable condition.

This only restricts construction, not close/abort. Group and Frame handles
stay independently closeable and shareable.

No external churn: every caller already builds via `create_group` /
`create_frame`, including the lite subscriber receive path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kixelated kixelated force-pushed the claude/amazing-heyrovsky-59675c branch from 6dcce9e to 15d4c18 Compare June 6, 2026 16:29
@kixelated kixelated merged commit 711e761 into dev Jun 6, 2026
1 check passed
@kixelated kixelated deleted the claude/amazing-heyrovsky-59675c branch June 6, 2026 16:51
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.

1 participant