diff --git a/quest/m0/README.md b/quest/m0/README.md index 2781ce6732..2fcab1aaee 100644 --- a/quest/m0/README.md +++ b/quest/m0/README.md @@ -42,6 +42,8 @@ Published API or wire breaks still land on dev; each quest's Plan says so. - [IETF FIN semantics](/quest/m0/ietf-fin-not-cancel.md) - a request stream FIN stops updates without cancelling, and REQUEST_UPDATE on a subscribe is parsed - [IETF early streams](/quest/m0/ietf-early-streams.md) - a moq-transport stream that arrives before SETUP is held until SETUP lands, never aborted - [SUBSCRIBE_TRACKS refusal](/quest/m0/ietf-subscribe-tracks.md) - a draft-18+ SUBSCRIBE_TRACKS gets NOT_SUPPORTED on its stream, not a session close +- [JS unknown bidi](/quest/m0/ietf-unknown-bidi.md) - an unknown moq-transport bidi stream type closes the session in JS, as in Rust +- [moq-lite early streams](/quest/m0/lite-early-streams.md) - a lite uni stream that arrives before SETUP is held until SETUP lands, and a second SETUP is fatal - [Request caps](/quest/m0/request-caps.md) - lite message sizes, IETF request IDs, and per-session announces and subscriptions are bounded - [noq reassembly cap](/quest/m0/noq-reassembly-cap.md) - noq carries quinn's stream reassembly cap and the connection receive window is finite by default - [qmux reset race](/quest/m0/qmux-reset-race.md) - qmux handles RESET_STREAM under one lock instead of panicking diff --git a/quest/m0/ietf-unknown-bidi.md b/quest/m0/ietf-unknown-bidi.md new file mode 100644 index 0000000000..e075b86053 --- /dev/null +++ b/quest/m0/ietf-unknown-bidi.md @@ -0,0 +1,36 @@ +# [XS] JS closes an IETF session on an unknown bidi stream type + +## Goal + +In `@moq/net`, a moq-transport bidi stream whose message type is unknown closes +the session with PROTOCOL_VIOLATION, as Rust and the JS uni path already do, +instead of aborting only that stream. A message a draft defines but we don't +support keeps the refuse-per-request rule +(#4685, #4610). + +## Plan + +Facts (`origin/main`, 2026-10-01): + +- JS: the `default:` arm of the bidi dispatch in + `js/net/src/ietf/connection.ts` logs and calls `stream.abort(...)`, so the + `#runBidis` catch never reaches `#violated` and the session stays open. +- JS uni already throws `ProtocolViolation` for an unknown type. +- Rust: `run_dispatch`'s `_` arm in `rs/moq-net/src/ietf/session.rs` returns + `UnexpectedStream`, which maps to PROTOCOL_VIOLATION. Rust has a uni test + (`an_unknown_uni_type_closes_the_session`) but no bidi one. + +Decided (2026-10-01): unknown is fatal; defined but unsupported is refused per +request. Rejected: making Rust lenient to match JS. + +- JS reads the bidi type with `u53()`, which throws `RangeError` for a valid + varint above 2^53 before the switch runs, so `#runBidis` never sees a + `ProtocolViolation` for it either. The uni path already reads `u62()`. + +Work: read the bidi type at full wire width (`u62()`), throw +`ProtocolViolation` from the JS default arm, and add a bidi test in both +languages that asserts the PROTOCOL_VIOLATION close code for a small unknown +type and one at or above 2^53. moq-lite is out of scope: its draft says an unknown stream type +MUST be reset and MUST NOT be fatal, and both implementations comply. + +Public API: none. Wire: none; fixes conformance. diff --git a/quest/m0/lite-early-streams.md b/quest/m0/lite-early-streams.md new file mode 100644 index 0000000000..b9d005c299 --- /dev/null +++ b/quest/m0/lite-early-streams.md @@ -0,0 +1,52 @@ +# [S] moq-lite streams that arrive before SETUP are held + +## Goal + +On lite-05 and later (the versions with a Setup Stream), a uni stream that +arrives before the peer's SETUP is held and handled once SETUP lands, never +aborted, in Rust and JS. A second Setup Stream closes the session with +PROTOCOL_VIOLATION in both languages, as the lite draft requires. + +## Plan + +Facts (`origin/main`, 2026-10-01): + +- Rust `lite::accept_setup` (`rs/moq-net/src/lite/session.rs`) aborts every + non-SETUP uni stream with `UnexpectedStream`. + `accept_request_skips_uni_stream_before_setup` in `rs/moq-net/src/server.rs` + pins that. An unknown `DataType` fails the handshake. +- JS lite has no pre-SETUP gate: `#runUni` in `js/net/src/lite/connection.ts` + handles SETUP like any other uni stream, so early groups already work. It + also accepts a second SETUP silently, and the `#runUnis` catch only stops the + stream, so throwing `ProtocolViolation` there would not close the session. + The `#runBidis` catch closes on `ProtocolViolation`, but with a bare + `close()` rather than the PROTOCOL_VIOLATION code. +- `drafts/draft-lcurley-moq-lite.md`: neither endpoint waits for the peer's + SETUP. A receiver MUST buffer only streams whose encoding depends on a + negotiated extension, and a second Setup Stream MUST close with + PROTOCOL_VIOLATION. Rust's abort is stricter than the draft. +- Lite-01 to lite-04 have no Setup Stream and are out of scope. + +Decided (2026-10-01): + +- ✅ Rust holds early uni streams until SETUP, then hands them to the normal + classifier, the same shape as + IETF early streams (#4686). Rust can't + route before SETUP anyway, since path and role come from it. Rejected: + processing immediately unless extension-dependent, which needs a session + before SETUP. +- ✅ The duplicate-SETUP check rides along, in both languages. In JS the uni + catch must close the session with the PROTOCOL_VIOLATION code, not copy the + bidi path's bare `close()`. + +The remaining JS gap is the duplicate SETUP and its close path; the hold is +Rust work unless JS turns out to need an extension-dependent hold. + +Crib from #4686: its `died_before_header` helper and peek-then-hold pattern. +QUIC stream credit bounds the held queue. Check whether JS lite must buffer any +extension-dependent stream type today, and hold those until SETUP. + +Tests: flip `accept_request_skips_uni_stream_before_setup` to expect the group +to be delivered. In both languages, a duplicate SETUP closes the session. + +Public API: none. Wire: none; matches the lite draft. diff --git a/quest/m1/README.md b/quest/m1/README.md index e096bebf5d..1bfeebf728 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -28,6 +28,7 @@ QUIC studies there on that rule. - [One route cost](/quest/m1/route-cost.md) - on `dev`, Warm and Cold collapse to one static route cost - [Delete removed cluster flags](/quest/m1/cluster-shims.md) - on `dev`, once a release has carried their refusals, `mesh` and `linger` leave `cluster::Config` - [Track tail interop](/quest/m1/track-tail-interop.md) - a Rust publisher ending a track with a group in flight is read to its end by the JS subscriber, and the reverse, in `just test interop` +- [moq-mux truncated group](/quest/m1/mux-truncated-group.md) - a test pins that moq-mux skips a truncated spliced group as aborted - [Merge queue](/quest/m1/merge-queue.md) - the required checks run on `merge_group`, so a stale green check can no longer break main - [Binding audio delay](/quest/m1/binding-surface.md) - moq-ffi and every wrapper configure and observe audio playout delay - [FFI shape](/quest/m1/ffi-shape/README.md) - the bindings mirror Rust's layers: net at the root, then media, json, flate, audio, and video namespaces built from the handle below diff --git a/quest/m1/mux-truncated-group.md b/quest/m1/mux-truncated-group.md new file mode 100644 index 0000000000..fb44cad34f --- /dev/null +++ b/quest/m1/mux-truncated-group.md @@ -0,0 +1,23 @@ +# [XS] moq-mux skips a truncated spliced group + +## Goal + +A moq-mux test feeds its consumer a spliced group whose continuation can never +arrive and asserts the group is skipped as aborted, not read as a short clean +group. + +## Plan + +Start after #4689 lands: it makes `group::Consumer` reads and `finished()` fail +on a truncated spliced group (`Error::Dropped` for a pruned seam, else the dead +route's error), where they used to end clean. moq-mux's consumer already drops a group +whose read fails, but only code reading verified that; no test covers a spliced +group. Build one with the moq-net resume test fixtures, or the smallest public +path to a pruned seam. + +Public API: none. Wire: none. + +## Related + +- [#4689](https://github.com/moq-dev/moq/pull/4689) - the truncated-group behavior this test pins +- [#4491](https://github.com/moq-dev/moq/pull/4491) - the `resume.rs` splice path