Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions quest/m1/auth/request-token.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,33 @@ no longer fails the session; this quest gives it meaning.
## Plan

- Decode with the SETUP option's structure and rules
(`rs/moq-net/src/ietf/token.rs`, `js/net/src/ietf/token.ts`): `USE_VALUE` yields the token, `REGISTER` is a value since we
advertise no `MAX_AUTH_TOKEN_CACHE_SIZE`, and `DELETE` or `USE_ALIAS`
closes with `PROTOCOL_VIOLATION`. Both decoder families change: the strict
(`rs/moq-net/src/ietf/token.rs`, `js/net/src/ietf/token.ts`): `USE_VALUE` yields the token. `REGISTER` closes the session with
`AUTH_TOKEN_CACHE_OVERFLOW` (0x13): we
advertise no `MAX_AUTH_TOKEN_CACHE_SIZE`, so the limit is 0, and draft-21
§8.9 says overflowing it MUST close the session. Only SETUP falls back to
USE_VALUE (§9.1.4). `DELETE` or `USE_ALIAS` closes with
`UNKNOWN_AUTH_TOKEN_ALIAS` (0x17): a cache size of 0 "prohibits the use
of token Aliases" (§9.1.3), so no alias is ever registered. §8.9 says to
reject the message, but 0x17 exists only as a Session Termination Code
(§12.2), not a Request Error Code. Record that close as a deviation
under "moq-transport" in `doc/concept/standard.md`. A token structure that does not
decode closes with `KEY_VALUE_FORMATTING_ERROR`. Paul Gregoire's
validator checks the REGISTER and malformed-token codes (decided
2026-10-04; aliases decided 2026-10-05). Both decoder families change: the strict
`decode_params!` path, where each request reads the repeatable key into an
ignored `Vec<Opaque>`, and draft-14's `Parameters::skip`, which consumes it
unread. `js/net` keeps every instance in `Parameters` and reads none.
- 0x13 and 0x17 join the shared session registry: `SessionError`
(`rs/moq-net/src/error.rs`) and `SessionCode` (`js/net/src/error.ts`).
Lite codes below 32 carry moq-transport's meaning, so add both rows to the
Session Error Codes table in `drafts/draft-lcurley-moq-lite.md` and to
`session_codes_round_trip`, as [Malformed moq-transport
input](/quest/m2/ietf-malformed-close.md) does for its codes. No
per-version mapping. That adds two codes to moq-lite's wire registry.
- Ship the decode-and-close change as this quest's first PR. It needs
neither [Relay tokens](/quest/m1/auth/relay-refresh.md) nor the
per-request lease, so the validator's REGISTER and malformed-token rows
need not wait for the rest of the quest.
- Fallback only: a request the session grant already covers is served
without verifying its token. Otherwise its token becomes an
`auth::Request` on the session's `auth::Handle`, the seam an AUTH stream's
Expand Down Expand Up @@ -64,7 +85,10 @@ no longer fails the session; this quest gives it meaning.
verifier; a REQUEST_UPDATE token keeps a subscription alive past the old
token's expiry; an expired request token ends only that request; with no
consumer a token-bearing request is refused `Unsupported`; one legacy and
one strict draft, Rust and JS.
one strict draft, Rust and JS. Regression tests for the decode: a
`REGISTER` token on a request closes with `AUTH_TOKEN_CACHE_OVERFLOW`, a
`DELETE` or `USE_ALIAS` one with `UNKNOWN_AUTH_TOKEN_ALIAS`, and a
malformed request token with `KEY_VALUE_FORMATTING_ERROR`.

Public API: additive on `moq_net::auth::Request` (the request it belongs
to) and on `moq_auth::Client` (the per-request lease). Wire: none new; the parameter already exists in every supported draft.
Expand Down
2 changes: 2 additions & 0 deletions quest/m2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,6 @@ waiting on an upstream release waits in [m4](/quest/m4/README.md).
- [moq-transport cluster peers](/quest/m2/ietf-cluster-peers.md) - an extended cluster draft lets moq-transport relays peer again, carrying the cluster route layer
- [MSFTS convergence](/quest/m2/msfts-convergence.md) - the demultiplexed TS lane converges on MSFTS where the two still differ: program tables and the ES payload unit
- [VAAPI encode and decode](/quest/m2/video-vaapi.md) - H.265 encode and decode, pre-generated bindings, and pooled resize surfaces, including the moq-dev/vaapi release that carries them
- [Malformed moq-transport input](/quest/m2/ietf-malformed-close.md) - malformed draft-18 and draft-21 control input closes the session with the draft's code, or PROTOCOL_VIOLATION where a code is a real burden and the fallback is recorded in `doc/concept/standard.md`, in moq-net and js/net
- [moq-transport request codes](/quest/m2/ietf-request-codes.md) - LARGEST_OBJECT, Range Filters (INVALID_FILTER), reserved namespaces, and RENDEZVOUS_TIMEOUT get the draft's answer, or a recorded fallback code, and the deliberate deviations are documented
- [Dead worklet config](/quest/m2/worklet-dead-config.md) - delete moq-boy's unused worklet plugin and hang's unused `?worker&url` typings
127 changes: 127 additions & 0 deletions quest/m2/ietf-malformed-close.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# [L] Malformed moq-transport input closes the session

## Goal

On draft-18 and draft-21, every malformed control input that the draft
says ends the session does end it, in both `rs/moq-net` and `js/net`.
It closes with the draft's code, or with PROTOCOL_VIOLATION where a code
is a real burden and the fallback is recorded in
`doc/concept/standard.md`. Today moq-net logs most of these and keeps
going. The cases come from Paul Gregoire's publisher validator
(github.com/mondain/moq-contribution-interop-runner), run in observed mode
against a client built on moq-net 0.17.0. Each case below gives the
runner's scenario name, where it has one:

- An unknown control message type (`receive-unknown-message-type`).
- A message length that doesn't match its body, or a body cut short by FIN
(`d21-message-body-length-mismatch`,
`d21-request-message-truncated-at-fin`).
- A zero-length namespace field, or a namespace or full track name over
4096 bytes.
- A Request ID with the wrong parity: INVALID_REQUEST_ID.
- A GROUP_ORDER outside 1..2.
- An understood key-value with a bad serialization:
KEY_VALUE_FORMATTING_ERROR
(`receive-understood-key-value-invalid-serialization`).
- A second GOAWAY on the control stream or on a single request stream, or
a New Session URI over 8192 bytes: PROTOCOL_VIOLATION. A first GOAWAY on
a request stream is legal (draft-18 §10.4, draft-21 §9.2) and leaves the
session up.
- A draft-18 GOAWAY cutoff Request ID with the wrong parity:
INVALID_REQUEST_ID, not a redirect.
- A server SETUP carrying AUTHORITY or PATH: INVALID_AUTHORITY or
INVALID_PATH.
- Repeated unknown or GREASE Setup Options are accepted
(`setup-unknown-grease-options-and-duplicates`,
`d21-grease-setup-options`). Only a repeated known option is a duplicate.
- A server SETUP AUTHORIZATION TOKEN that is malformed closes with
KEY_VALUE_FORMATTING_ERROR. A REGISTER that overflows our cache (size 0)
falls back to USE_VALUE, as draft-21 §9.1.4 requires.
- A SUBSCRIBE_TRACKS prefix with more than 32 fields or over 4096 bytes
closes with PROTOCOL_VIOLATION, instead of being refused per request
before it is decoded.

## Plan

Decided with the maintainer on 2026-10-04 and 2026-10-05:

- **Add the missing session codes to the shared registry.**
INVALID_REQUEST_ID (0x4), INVALID_PATH (0x8) and INVALID_AUTHORITY
(0x19) become `SessionError` variants (`rs/moq-net/src/error.rs`) and
`SessionCode` entries (`js/net/src/error.ts`). Their values are the same
in drafts 18 and 21. That registry is moq-lite's too, and lite codes
below 32 carry moq-transport's meaning, so add the rows to the Session
Error Codes table in `drafts/draft-lcurley-moq-lite.md` and to
`session_codes_round_trip`. No per-version mapping. Where a code is a
real burden, the PROTOCOL_VIOLATION fallback in the Goal applies.
- **Regression tests, not a CI job for the validator.** Add one session or
codec test per case, each failing without its fix and asserting the
draft's code, or PROTOCOL_VIOLATION where the fallback is recorded in
`doc/concept/standard.md`. Include a positive case: a first GOAWAY on a
request stream keeps the session open. Run the external runner once by
hand before the PR is marked ready: observed mode,
`--publisher-no-fetch`, one scenario per run, on a track of about
150 kbps. A full-bitrate track floods the runner's event log and aborts
the run.
- **Mirror in `js/net/src/ietf`** in the same PR.
- Request tokens are out of scope here. AUTH_TOKEN_CACHE_OVERFLOW and the
request-token decode belong to [Request
tokens](/quest/m1/auth/request-token.md).

Where each case lives, mapped on 2026-10-04 (paths under
`rs/moq-net/src/`):

- The SETUP/GOAWAY uni stream runs as a task spawned inline in `run_unis`
(`ietf/session.rs`, around lines 751-874). SETUP and SETUP-parameter
decode failures there already close the session; only `run_goaway`'s
error is just logged. The gated server accept, which reads SETUP early,
calls `run_goaway` a second time (`goaway_recv`, around line 375) and
does the opposite: any error, even a stream reset, ends the session.
Make both sites agree, ideally by filtering inside `run_goaway`. Close
on the errors `is_protocol_violation` (`ietf/subscriber.rs`) marks as
the peer's fault, not on every error: a reset or transport failure on
that stream must stay non-fatal, as `died_before_header` keeps it
elsewhere. That fixes the second GOAWAY,
the oversize URI, and an unknown type on that stream. On request
streams, an error from a follow-up message is only logged at debug in
`ietf/publisher.rs` (around line 500); promote decode errors there with
the same filter.
- `died_before_header` (`ietf/session.rs`) treats a body cut short at FIN as
the stream dying. A frame whose declared length passes FIN is malformed.
Responses decoded at `ietf/publisher.rs` (about lines 1858 and 1937) lack
the trailing-bytes check that the first message has.
- Namespace bounds: `ietf/namespace.rs` checks only the 32-part limit. The
track name in `ietf/subscribe.rs` is unbounded.
- Request ID parity is known only in `Control::new` (`ietf/control.rs`), so
thread it to where IDs are decoded.
- `ietf/group.rs` maps an invalid GROUP_ORDER, or 0, to Descending.
- KV serialization errors must be told apart from other decode errors.
`From<&Error> for SessionError` (`error.rs`) maps every `Decode` error to
PROTOCOL_VIOLATION, and `ietf/session.rs` and `ietf/publisher.rs`
hard-code it in a few places.
- GREASE: `ietf/parameters.rs` (around line 122) keys every unknown ID as
`Unknown(u64)` in a HashMap, so any repeat fails as `Duplicate`. In this
tree that includes the plain duplicate-unknown case the report says now
passes, so check which build the report ran.
- Client SETUP: `decode_peer_setup` (`ietf/session.rs`) ignores AUTHORITY
and PATH and never parses the server's token. Reuse `ietf/token.rs`,
which already does the server-side fallback.
- SUBSCRIBE_TRACKS is refused before its prefix is decoded
(`ietf/publisher.rs`, about lines 547 and 1609). Decode the prefix first,
then refuse.

Test models: `an_unknown_uni_type_closes_the_session` and
`an_unknown_bidi_type_closes_the_session` in `ietf/session.rs`, which drive
`ScriptedSession`. Codec tests live in `ietf/parameters.rs` and
`ietf/subscribe.rs`. Add each new decode error to the `ietf_wire` fuzz
regressions where it fits.

Public API: new `SessionError` variants and `SessionCode` entries. Wire:
new moq-lite session codes, with moq-transport's values; moq-transport
behaviour moves closer to the drafts.

## Related

- [Request tokens](/quest/m1/auth/request-token.md) - owns request-token decode and AUTH_TOKEN_CACHE_OVERFLOW
- [Request caps](/quest/m0/request-caps.md) - bounds peer lengths and counts; shares the decode paths
- [moq-transport request codes](/quest/m2/ietf-request-codes.md) - the request-level half of the same validator report
67 changes: 67 additions & 0 deletions quest/m2/ietf-request-codes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# [M] moq-transport requests get the draft's answer

## Goal

On draft-18 and draft-21, `rs/moq-net` and `js/net` answer these requests
the way the drafts say, without closing the session. Each answer uses the
draft's code, or an existing code where the exact one costs more than it
is worth and the fallback is recorded in `doc/concept/standard.md`:

- **LARGEST_OBJECT (0x09) in REQUEST_OK and REQUEST_UPDATE_OK** is
accepted. The draft makes it a MUST when objects exist. Today
`ietf/request.rs` allows only 0x08 and ACTIVE_COUNT, so a conformant
peer's reply closes the session with PROTOCOL_VIOLATION.
- **Range Filters** get REQUEST_ERROR INVALID_FILTER (0x36), not
NOT_SUPPORTED (draft-19 and later). We advertise no MAX_FILTER_RANGES, so
the default of 0 applies (draft-21 §9.1.6) and every Range Filter is over
the limit. This covers SUBSCRIBE and FETCH alike: both refuse Range
Filters today (`ietf/publisher.rs`, around lines 605 and 1292).
- **Draft-21 reserved namespaces:** draft-21 §2.4.2 and the `.session`
rules (§6.5) ask for REQUEST_ERROR DOES_NOT_EXIST, without passing the
request to the application, for:
- a request for an unrecognized `.session` track or namespace;
- a `.session` namespace with an empty track name;
- a namespace whose first field is exactly `.`.

Only these short-circuit. Any other reserved namespace (a first field
starting with `.`) still goes to the application, as §2.4.2 requires.
- **RENDEZVOUS_TIMEOUT:** our wait is capped at 0, which draft-21 §9.20.7
allows. If a SUBSCRIBE asks to wait and there is no publisher, it gets
TIMEOUT at once. A request that is absent or 0 still gets DOES_NOT_EXIST.
This fixes the two quic-zig `rendezvous-timeout` cells in the interop
matrix.
- **OBJECT_DELIVERY_TIMEOUT (0x02) and SUBGROUP_DELIVERY_TIMEOUT (0x06)**
on REQUEST_UPDATE are accepted and ignored, as SUBSCRIBE already does
(`ietf/subscribe.rs`). Today `ietf/request_stream.rs` refuses both as
unsupported.

## Plan

Decided with the maintainer on 2026-10-04 and 2026-10-05:

- **Deliberate deviations stay.** DUPLICATE_SUBSCRIPTION and PREFIX_OVERLAP
are never sent, because moq-net deduplicates. OBJECT_DELIVERY_TIMEOUT and
SUBGROUP_DELIVERY_TIMEOUT never drop an object or reset a subgroup: they
would need wall-clock delivery deadlines, and moq-net's latency
enforcement is presentation time only (#2890). The rendezvous wait is
capped at 0. Each deviation gets a line under
"moq-transport" in `doc/concept/standard.md`, so the next validator run
has a reason to point at.
- **Exact codes when cheap.** INVALID_FILTER becomes a request code. Where
one costs more than it is worth, the fallback in the Goal applies.
- **TIMEOUT, not a timer.** The 0 cap means nothing waits, so no request is
ever parked. The reply depends only on whether the parameter asked for a
wait. `ietf/subscribe.rs` parses RENDEZVOUS_TIMEOUT and discards it
(around line 140); keep it as far as the publisher's track lookup
(`ietf/publisher.rs`, around line 617). `Error::Timeout` already maps to
TIMEOUT (`ietf/error.rs`).
- One regression test per case, failing without its fix and asserting the
draft's code, or the fallback recorded in `doc/concept/standard.md`.
Mirror in `js/net/src/ietf` in the same PR.

Public API: none expected. Wire: none new; replies move closer to the
drafts.

## Related

- [Malformed moq-transport input](/quest/m2/ietf-malformed-close.md) - the session-level half of the same validator report
Loading