feat(moq-net): hook up the rest of moq-lite-05 wire (TRACK_INFO, SUBSCRIBE_END, frame timestamps)#1963
Conversation
…CRIBE_END, frame timestamps) Builds on #1954 (SETUP + PATH) to implement the remaining moq-lite-05 wire changes on both the Rust (`rs/moq-net`) and JS (`js/net`) sides. Everything stays gated behind the opt-in `Lite05Wip` version (still excluded from `ALPNS` and `Versions::all`), so it is additive: no shipping wire-format, model, or public-API change. Wire changes (version-gated on a new `Version::has_track_stream()` / `hasTrackStream()`): - Track stream (`ControlType`/`StreamId` 0x6) + `TRACK_INFO` carrying the publisher's immutable `priority` / `ordered` / `max_latency` / `timescale`. The publisher serves it; decode rejects a zero timescale. - `SUBSCRIBE_OK` trimmed to just the resolved start group (publisher properties moved to `TRACK_INFO`); new `SUBSCRIBE_END`; the response discriminators become OK=0x0 / END=0x1 / DROP=0x2 on lite-05 (OK=0x0 / DROP=0x1 on 03/04). The publisher sends a resolved-group OK eagerly and a `SUBSCRIBE_END` before FIN; the subscriber drains OK/END/DROP until FIN. - Per-frame timestamp: the publisher stamps each frame with a wall-clock millisecond timestamp (`Time::now()` / `Date.now()`) and writes a zigzag delta before the frame size; the subscriber decodes and discards it. The timescale is fixed at 1000, so `hang`/`moq-mux`/`ffi` are untouched (the moq-net wire timestamp is redundant with hang's in-payload timestamp but lets the media-agnostic relay see timing without parsing payloads). The JS side additionally gains the SETUP stream that #1954 never mirrored: each endpoint sends an (empty) SETUP on a unidirectional stream and reads the peer's. The browser conveys its path via the WebTransport URL, so it never sends a Path parameter. `connect.ts` maps the negotiated lite-05 ALPN to the right version; it stays opt-in (not advertised by default), matching Rust. Datagram delivery is deferred to a follow-up. Docs (`doc/concept`, `doc/bin/relay`) and a live e2e test are still outstanding. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Sorry @kixelated, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
WalkthroughThis PR adds moq-lite Draft 05 WIP support across the Rust and JavaScript networking stacks. It introduces Track and TrackInfo messages, a Setup message, and a SubscribeEnd response. SubscribeOk is adjusted to carry only the resolved start group in Draft 05. Publisher and subscriber loops are updated for Track handling, trailing subscribe responses, and per-frame timestamp deltas. ALPN routing is extended to recognize the Draft 05 protocol. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
js/net/src/lite/track.ts (1)
75-78: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAdd a
timescale === 0guard on encode to mirror Rust.Rust
TrackInfo::encode_msgrejects a zero timescale (EncodeError::InvalidState), but the JSencodeonly validates the version, so aTrackInfoconstructed withtimescale: 0would be sent and only rejected by the peer's decoder. Guard on encode for symmetry and to fail fast locally.♻️ Proposed guard
async encode(w: Writer, version: Version): Promise<void> { if (!hasTrackStream(version)) throw new Error("TRACK_INFO requires moq-lite-05+"); + if (this.timescale === 0) throw new Error("TRACK_INFO timescale must be non-zero"); return Message.encode(w, this.#encode.bind(this)); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@js/net/src/lite/track.ts` around lines 75 - 78, Add a local encode-time validation in TrackInfo.encode so it rejects a timescale of 0 before calling Message.encode, matching the Rust TrackInfo::encode_msg behavior. Keep the existing hasTrackStream(version) check, then add a guard that throws an error when this.timescale is zero so invalid TrackInfo instances fail fast on the sender side instead of being emitted and rejected later by the peer.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@js/net/src/lite/publisher.ts`:
- Around line 175-179: The lite-05 subscribe flow in publisher.ts is using the
request startGroup or 0 when building SUBSCRIBE_OK, while the track reader is
positioned separately, so the acknowledged start can differ from actual
delivery. In the SUBSCRIBE handling path that builds the SubscribeOk and opens
the track reader, resolve the absolute start group once, pass that resolved
value into the track reader/positioning logic, and reuse the same value when
constructing SubscribeOk so both paths stay aligned.
In `@js/net/src/lite/subscribe.ts`:
- Line 332: The exported response helper functions are missing required
documentation. Add JSDoc for encodeSubscribeResponse and
decodeSubscribeResponse, and also document the other exported symbols referenced
in the same area (the helpers around lines 402-404) so every exported symbol in
subscribe.ts has a clear description, parameters, and return behavior where
applicable.
- Line 330: The exported SubscribeResponse union in subscribe.ts has been
widened with a new end variant, which changes the public API and can break
exhaustive downstream type handling. Update the SubscribeResponse and related
subscribe wire types so the stable exported type stays versioned, or move the
end case into an internal wire-only type used by the Lite05Wip path; keep the
public re-export from index.ts aligned with the stable API and use a separate
version-specific response type if needed.
In `@rs/moq-net/src/lite/publisher.rs`:
- Around line 471-473: The subscribe flow in publisher::run should use one
resolved start value for both the SUBSCRIBE_OK response and track playback.
Compute the resolved Option<u64> once from subscribe.start_group or
track.latest(), then pass that value into run_track instead of recomputing
track.latest() inside run_track. Update the start_at call in run_track to use
the passed-in resolved start so the advertised group and the actual served start
stay consistent.
In `@rs/moq-net/src/lite/subscribe.rs`:
- Around line 321-324: `SubscribeResponse` is a public enum whose variants are
part of the stable API, so adding `End` directly expands the exhaustiveness
surface for downstream matches. Update `SubscribeResponse` in `subscribe.rs` to
either mark the enum with `#[non_exhaustive]` and keep `Ok`, `End`, and `Drop`
as the public shape, or remove `End` from the public enum if it should not be
part of the API. If `End` remains public, add rustdoc to the `SubscribeResponse`
and `SubscribeEnd` symbols so the new variant is documented.
---
Nitpick comments:
In `@js/net/src/lite/track.ts`:
- Around line 75-78: Add a local encode-time validation in TrackInfo.encode so
it rejects a timescale of 0 before calling Message.encode, matching the Rust
TrackInfo::encode_msg behavior. Keep the existing hasTrackStream(version) check,
then add a guard that throws an error when this.timescale is zero so invalid
TrackInfo instances fail fast on the sender side instead of being emitted and
rejected later by the peer.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3aa45db0-8970-4ad6-83cb-c8960362cb47
📒 Files selected for processing (16)
js/net/src/connection/connect.tsjs/net/src/lite/connection.tsjs/net/src/lite/publisher.tsjs/net/src/lite/setup.tsjs/net/src/lite/stream.tsjs/net/src/lite/subscribe.tsjs/net/src/lite/subscriber.tsjs/net/src/lite/track.tsjs/net/src/lite/version.tsrs/moq-net/src/lite/mod.rsrs/moq-net/src/lite/publisher.rsrs/moq-net/src/lite/stream.rsrs/moq-net/src/lite/subscribe.rsrs/moq-net/src/lite/subscriber.rsrs/moq-net/src/lite/track.rsrs/moq-net/src/lite/version.rs
- publisher: resolve the lite-05 start group once in run_subscribe and use it to position the track there, instead of recomputing track.latest() inside run_track. The SUBSCRIBE_OK value and the served start can no longer diverge if a new group arrives between the two reads. - js subscribe: document the exported encodeSubscribeResponse / decodeSubscribeResponse helpers. - js track: reject a zero timescale on TrackInfo encode too (mirrors the Rust side) so an invalid value fails fast on the sender. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summary
Builds on #1954 (SETUP + PATH) to hook up the rest of the moq-lite-05 wire changes on both the Rust (
rs/moq-net) and JS (js/net) sides. Everything stays gated behind the opt-inLite05Wipversion (still excluded fromALPNS/Versions::all), so this is additive: no shipping wire-format, model, or public-API change (thelitemodule is private), which is why it targetsmainlike #1954.All branches are version-gated on a new
lite::Version::has_track_stream()(Rust) /hasTrackStream()(JS).Wire changes
TRACK_INFO(ControlType/StreamId0x6): carries the publisher's immutablepriority/ordered/max_latency/timescale. The publisher serves it (recv_track); decode rejects a zero timescale. The subscriber does not proactively open it yet (properties remain unused, as before).SUBSCRIBE_OK+ newSUBSCRIBE_END: lite-05SUBSCRIBE_OKcarries only the resolved start group (publisher properties live inTRACK_INFO). Response discriminators becomeOK=0x0 / END=0x1 / DROP=0x2on lite-05 (OK=0x0 / DROP=0x1on 03/04). The publisher sends a resolved-group OK eagerly and aSUBSCRIBE_ENDbefore FIN (run_tracknow returns the last sequence); the subscriber drains OK/END/DROP until FIN.Time::now()/Date.now()) and writes a zigzag delta before the frame size; the subscriber decodes and discards it.JS-specific
DataId.Setup = 1) and reads the peer's. The browser conveys its path via the WebTransport URL, so it never sends a Path parameter (per spec).connect.tsmaps the negotiated lite-05 ALPN →DRAFT_05_WIP. It stays opt-in (not advertised by default), matching Rust.Design decisions
hang/moq-mux/ffiare untouched. The moq-net wire timestamp is redundant with hang's in-payload (microsecond) timestamp but lets the media-agnostic relay see timing without parsing payloads (per the "timestamp on the wire" intent).1000(ms) inTRACK_INFO.PATH status
The SETUP-PATH wire is fully piped for the realistic transports: WebTransport (path from the URL), native raw (
Client::with_path→ SETUP →Server::accept_request().path()), and the relay's internal qmux listener (scopes by it).Known gap (not addressed here): a qmux/raw session to the main relay can't scope by the in-band SETUP path —
moq_native::Request::url()isNonefor the WebSocket backend andRequestdoesn't surface the SETUP path, so only the internal listener honors it. Closing it needs a moq-native two-phase accept + relay wiring and has a rejection-timing tension (HTTP status vs reading SETUP after the handshake).Test plan
cargo clippy -p moq-net -p moq-native --all-targets -- -D warningsclean (via nix toolchain)cargo test -p moq-net— 374 pass, incl. new round-trip tests forTRACK_INFO, lite-05SUBSCRIBE_OK,SUBSCRIBE_END, and response discriminatorsjust js checkgreen (all packages, biome clean)doc/concept,doc/bin/relay) not yet updated🤖 Generated with Claude Code
(Written by Claude Opus 4.8)