fix(moq-net): an IETF group fetch fill is complete or refused, from the wanted frame - #4544
Conversation
…he wanted frame - FETCH_OK's End Location inside the group is a promise: a stream short of it, or past it, fails the group instead of caching it. - The first fetch object must spell out every field it could inherit. - The upstream FETCH asks from group::Request::frame_start and numbers the fill from it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: df72c338c2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
| // An exclusive End Location inside the group is its Largest Object plus one, so | ||
| // the stream owes every object up to it. One past the group covers it whole. | ||
| let end = (end.group == sequence).then_some(end.object); |
There was a problem hiding this comment.
Validate whole-group responses before caching them
When the normal whole-group FETCH_OK reports (sequence + 1, 0), as this repository's publisher does, this line converts the boundary to None. A stream carrying only an initial prefix followed by a clean FIN therefore bypasses both end checks, and recv_group_fetch finishes and caches that prefix as a complete group. This leaves the principal truncation case unfixed, so responses without a concrete object boundary need another verifiable completion condition before the producer is finished.
AGENTS.md reference: rs/moq-net/AGENTS.md:L9-L9
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Not changing this: the maintainer confirmed that a clean FIN marks a complete whole-group fetch (option 1 under "Open decision" in the description). No draft carries an explicit end-of-group signal on fetch streams from draft-16 on, draft-14 to 16 define the last object of a whole-group FETCH as the group's final object, and every draft treats gaps before FETCH_OK's end as nonexistent objects once the stream FINs. A peer that FINs early has published a malformed track, which neither we nor moxygen can detect. A reset is still an error, and a concrete End Location inside the group is still enforced.
(Written by Claude Opus 5.5)
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed head: df72c33
Direction: a useful correctness improvement. Requesting from the wanted frame, preserving object IDs, and refusing holes or a violated concrete end boundary is preferable to caching a knowingly incomplete answer.
No additional actionable defect found in the changed decoder/fill paths. I reviewed the existing r4139139293 discussion: a clean FIN is deliberately the completion proof for a whole-group response with no concrete object boundary. I am not duplicating that accepted protocol limitation as a defect; resets and concrete End Location mismatches remain errors. The first-object inheritance check also prevents fabricating a prior object from request context.
Verification: static review of the changed paths and scripted regressions, including the requested-tail case; no Rust tests executed.
(Written by OpenAI)
Problem
#4276 fills a relay cache miss from an IETF upstream with a standalone FETCH of one group. Three Codex findings merged unanswered, and the maintainer ruled each blocks the moxygen line (
quest/m1/moxygen/fetch-fill.md):group::Request::frame_start, and neverstart_atthe producer (r4113942733).Approach
All in
rs/moq-net/src/ietf/subscriber.rs:ProtocolViolation. An End Location past the group covers it whole, and the stream's FIN ends it (see the decision below).decode_fetch_objectrefuses a first object that leaves any field to a prior object: Group ID, Object ID, Subgroup (Prior/PriorPlusOne), or Publisher Priority. This is the drafts' own rule ("If the first Object in the FETCH response uses a flag that references fields in the prior Object, the Subscriber MUST close the session with a PROTOCOL_VIOLATION", draft-15 onward), and it covers the joining-fetch fill too. An object in another group, or with an ID before the next one, is nowProtocolViolationinstead ofUnsupported. A skipped ID is stillUnsupported: the drafts allow the hole, but the model cannot hold it.(group, frame_start). The accepted producer isstart_atit, asframe_start's docs require, and the decoder numbers from it. This mirrors the lite subscriber.Three regression tests, each failing without its fix:
a_group_fetch_refuses_a_first_object_that_inherits,a_group_fetch_must_reach_its_end_location, anda_group_fetch_asks_from_the_wanted_frame(end to end throughrun_group_fetch, checking the FETCH on the wire).Decision: the whole-group end signal
The quest asks for a wire signal marking a complete whole-group fetch, and says to ask if the drafts offer none. They offer no explicit one:
Options:
Impact
moq-net.(group, frame_start)instead of(group, 0). The End Location is unchanged (whole group).ProtocolViolation. So is an object outside the requested group or start, or a stream that doesn't match a concrete FETCH_OK End Location. As before, these reset only the stream, not the session.js/netchange: the encoding is unchanged.Alternatives
(group + 1, 0), which names no object count (the accepted decline in r4114050992).Follow-ups
subgroup_ok = falseand is refused asUnsupported. So a cache miss on a one-datagram-per-group track fails through the relay, which the line promises to carry. Suggest a quest.ProtocolViolationresets only that stream. The drafts say to close the session. Low priority, and it may be deliberate.just test interop --allwas not run. The matrix speaks moq-lite media and never issues an IETF FETCH, so it cannot exercise this change.(Written by Claude Opus 5.5)
🤖 Generated with Claude Code