Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
09558a2
quest: claim ietf-fetch-only
kixelated Oct 7, 2026
c02ed6f
feat(moq-net): serve IETF TRACK_STATUS and keep timestamps without pr…
kixelated Oct 7, 2026
4dcd043
feat(moq-net): learn fetch-only IETF tracks from TRACK_STATUS
kixelated Oct 7, 2026
ee8656b
docs: TRACK_STATUS for fetch-only demand; carry the opt-out into its …
kixelated Oct 7, 2026
594e25e
style: rustfmt
kixelated Oct 7, 2026
4ff8c90
fix(moq-net): fail a pending TRACK_STATUS with the session
kixelated Oct 7, 2026
047e1ae
quest: link the opt-out quest to its PR
kixelated Oct 7, 2026
4df226b
Merge origin/main into quest/m1/ietf-fetch-only
kixelated Oct 7, 2026
39dfccf
feat(moq-net): ask for an IETF track's properties once per copy
kixelated Oct 7, 2026
d51ff3d
Merge remote-tracking branch 'origin/main' into iterate/ietf-fetch-only
kixelated Oct 7, 2026
eaac1b3
Merge remote-tracking branch 'origin/main' into fetch-only-iterate
kixelated Oct 7, 2026
793ae1b
feat(moq-net): a refused TRACK_STATUS refuses the fetch-only request
kixelated Oct 7, 2026
ba732bb
fix(moq-net): a resumed draft-17 copy takes SUBSCRIBE_OK's timescale
kixelated Oct 7, 2026
3ad859e
docs: rewrap the TRACK_STATUS paragraph
kixelated Oct 7, 2026
c9d766e
Merge remote-tracking branch 'origin/main' into fetch-only-iterate
kixelated Oct 7, 2026
ac1e723
style: cargo fmt the timescale assertion
kixelated Oct 7, 2026
fb4f866
Merge remote-tracking branch 'origin/main' into quest/m1/ietf-fetch-only
kixelated Oct 8, 2026
e0a40d2
fix(moq-net): adapt TRACK_STATUS and tests to main's epoch and untime…
kixelated Oct 8, 2026
0a5e3e1
feat(moq-net): draft-17 fetch-only demand still subscribes
kixelated Oct 8, 2026
90cada6
quest: drop the link to the finished ietf-fetch-only quest
kixelated Oct 8, 2026
1388489
Merge remote-tracking branch 'origin/main' into quest/m1/ietf-fetch-only
kixelated Oct 8, 2026
7cdf309
Merge remote-tracking branch 'origin/main' into quest/m1/ietf-fetch-only
kixelated Oct 8, 2026
5a47135
fix(moq-net): gate TRACK_STATUS_OK's parameters per draft like SUBSCR…
kixelated Oct 8, 2026
4f5ce1c
Merge remote-tracking branch 'origin/main' into quest/m1/ietf-fetch-only
kixelated Oct 8, 2026
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
6 changes: 3 additions & 3 deletions doc/concept/standard.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ maps everything else to "not supported" or a harmless equivalent. The
subset drops. What a peer actually observes against this implementation:

- **Pull.** Subscribers ask. Single-track `PUBLISH` offers are declined; announce a namespace and serve the resulting subscriptions. Announcements go out unsolicited, and we also ask for every prefix we may discover. The solicit `SETUP` option makes us wait to be asked. Without it, a draft 16+ peer also gets each match as a `NAMESPACE` on its `SUBSCRIBE_NAMESPACE` stream, so it hears the namespace twice. We never send `PUBLISH`: on drafts 16 and 17 a `SUBSCRIBE_NAMESPACE` asking only for `PUBLISH` is refused, one asking for both gets only `NAMESPACE`, and any other Subscribe Options value closes the session.
- **History is one group.** On drafts 14 through 19 a `FETCH` returns one whole group from the cache, or the saved prefix of the group a new subscription just joined. A range of groups is refused, as is any `FETCH` on draft 20 and later. JavaScript publishing refuses every `FETCH`. Datagrams are never fetchable.
- **Timing.** A track whose `SUBSCRIBE_OK` declares no `TIMESCALE` is untimed, as is every track on drafts 14 through 16. A reader that never subscribed reads a standalone fetch untimed.
- **History is one group.** On drafts 14 through 19 a `FETCH` returns one whole group from the cache, or the saved prefix of the group a new subscription just joined. A range of groups is refused, as is any `FETCH` on draft 20 and later. JavaScript publishing refuses every `FETCH`. Datagrams are never fetchable. A Rust reader that only fetches learns the track from `TRACK_STATUS` instead of subscribing, so a finished track stays fetchable; draft 17 still subscribes, since its `TRACK_STATUS` answer cannot say whether the track is timed.
- **Timing.** A track whose `SUBSCRIBE_OK` declares no `TIMESCALE` is untimed, as is every track on drafts 14 through 16. A Rust reader that never subscribed takes the units from `TRACK_STATUS` on draft 18 and later.
- **Strict SETUP and parameters.** Repeated unknown `SETUP` options, GREASE included, are accepted if well-formed; a repeated known option is rejected. On draft 17 and later, a `GROUP_ORDER` other than Ascending (1) or Descending (2) closes the session. Message parameters follow the negotiated draft's lists: drafts 14 and 15 ignore an unknown or misplaced parameter, draft 16 ignores one defined only for another message but closes the session on an unknown one, and draft 17 and later close it on both.
- **One credential per session**, carried in `SETUP` and forwarded to the [auth server](/bin/relay/auth#the-contract) unverified. A token attached to an individual request is ignored. An alias reference or a second token closes the session.
- **Refused, not fatal.** A legal request this stack does not serve is rejected on its own and the session stays up: `FORWARD=0`, range filters, `TRACK_STATUS`, `SUBSCRIBE_TRACKS`, and the fetch forms above. On draft 19 and later, and in Rust on drafts 14 through 16, a subscription update may change only priority; any other update ends that subscription.
- **Refused, not fatal.** A legal request this stack does not serve is rejected on its own and the session stays up: `FORWARD=0`, range filters, `TRACK_STATUS` to a JavaScript publisher, `SUBSCRIBE_TRACKS`, and the fetch forms above. On draft 19 and later, and in Rust on drafts 14 through 16, a subscription update may change only priority; any other update ends that subscription.
- **Datagrams** are a single normal object at object 0, forwarded without renumbering. Anything else is dropped, and a malformed one closes the session. Rust and JavaScript both carry them on every draft.
- **Priority.** Higher is served first. The IETF default of 128 is this stack's 127, and a track that never sets one is 127.
- **Size.** An object extension block larger than 64 KiB ends that subgroup stream. The session stays up. This cap is ours, not the draft's.
Expand Down
1 change: 0 additions & 1 deletion quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ were done or not worth their cost.
- [Data consumer timestamps](/quest/m1/data-consumer-timestamps.md) - json and binary consumers return each value's timestamp, in Rust and every binding; snapshots add `latest()` beside an in-order `next()`
- [JS data consumer timestamps](/quest/m1/js-data-consumer-timestamps.md) - @moq/json and @moq/flate consumers return each value's timestamp, with snapshot `next()` and `latest()`
- [Draft-20 FETCH](/quest/m1/ietf-fetch-location.md) - a draft-20+ FETCH within one group is served from its LOCATION_FILTER, as older drafts are
- [Fetch without SUBSCRIBE](/quest/m1/ietf-fetch-only.md) - a relay fetches from an IETF upstream without subscribing, finished tracks included, with End of Track always reported
- [FETCH_OK properties](/quest/m1/fetch-ok-properties.md) - our FETCH_OK carries the track properties SUBSCRIBE_OK does, as draft 16+ requires
- [IETF object gaps](/quest/m1/ietf-object-gaps.md) - a gapped object ID is refused loudly like a subgroup in Rust and JS, and an overflowing one closes the session in Rust
- [JavaScript FETCH](/quest/m1/js-fetch.md) - browser publishers answer IETF FETCH through the JS ranges request surface
Expand Down
20 changes: 8 additions & 12 deletions quest/m1/fetch-ok-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,14 @@ meets draft 16+'s rule to include all of a track's properties.

## Plan

Found (2026-10-01) in #4647: a group FETCH with no
prior SUBSCRIBE_OK now reads its max age from FETCH_OK, but our publisher
sends an empty block.
Found (2026-10-01) in #4647: our publisher's FETCH_OK sends an empty
properties block. Our own subscriber no longer reads it (#4974): it learns a
track from SUBSCRIBE_OK or TRACK_STATUS_OK before any FETCH, and from draft 20
opts out with INCLUDE_PROPERTIES = 0. Other fetch-only readers still need it.

- `run_fetch_stream` (`rs/moq-net/src/ietf/publisher.rs`) sends FETCH_OK
with `properties: Default::default()`. `run_subscribe_stream` fills them in
its `ietf::SubscribeOk` encode. Share that code so the two can't diverge.
- Our group-fetch accept path reads only the max age from FETCH_OK and
hardcodes a microsecond timescale. Apply the same properties SUBSCRIBE_OK
does (timescale, priority, group order) there too.
- Drafts 14-15 allow the omission: MAX_CACHE_DURATION is a MAY there, and
FETCH_OK has no properties block. From draft 16, a relay MUST include all
Extension Headers / Properties associated with a track in FETCH_OK (d16
Expand All @@ -37,21 +35,19 @@ sends an empty block.
- Honour INCLUDE_PROPERTIES (0x35) on FETCH from draft 20, once
[Draft-20 FETCH](/quest/m1/ietf-fetch-location.md) serves draft-20 FETCH at
all (today our publisher refuses every one). It defaults to sending the
properties; at 0 the block is present but empty. `fetch.rs` decodes it on
draft 20+ and drops it, so it is not honoured today.
properties; at 0 the block is present but empty. `fetch.rs` decodes it into
`Fetch::properties_wanted`, which the publisher does not honour yet.
- Test per draft range: FETCH_OK round-trips the properties SUBSCRIBE_OK would
carry, a standalone FETCH's objects arrive stamped, and
INCLUDE_PROPERTIES = 0 empties the block.

Decided 2026-10-08: [Fetch without SUBSCRIBE](/quest/m1/ietf-fetch-only.md)
lands first, since both change how a fetch-only reader learns the track's
properties.
Decided 2026-10-08: #4974 (fetch without SUBSCRIBE) lands first, since both
change how a fetch-only reader learns the track's properties.

Public API: none. Wire: FETCH_OK gains its properties on drafts that define
the block. Interop: run `just test interop --all`.

## Required

- [Fetch without SUBSCRIBE](/quest/m1/ietf-fetch-only.md) - lands first ([#4974](https://github.com/moq-dev/moq/pull/4974)); it asks TRACK_STATUS for the same properties when a publisher omits them
- [Draft-20 FETCH](/quest/m1/ietf-fetch-location.md) - draft-20 FETCH is served at all, and edits the same `run_fetch_stream`

48 changes: 0 additions & 48 deletions quest/m1/ietf-fetch-only.md

This file was deleted.

8 changes: 1 addition & 7 deletions quest/m2/pipeline-fetch-info.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ and passes the origin's consistency check.
## Plan

Decided 2026-10-07, while landing #4974 (fetch-only IETF demand), which keeps
a sequential TRACK_STATUS before the first FETCH. Decided 2026-10-08: it
Requires [Fetch without SUBSCRIBE](/quest/m1/ietf-fetch-only.md) (#4974),
since that adds the TRACK_STATUS this pipelines.
a sequential TRACK_STATUS before the first FETCH.

Why the round trip exists (facts, 2026-10-07): nothing in a FETCH request
needs the info. Two serial gates do:
Expand Down Expand Up @@ -49,7 +47,3 @@ released before its route's info passes. Measure the first-fetch latency
before and after.

Public API: none. Wire: none (ordering only).

## Required

- [Fetch without SUBSCRIBE](/quest/m1/ietf-fetch-only.md) - adds the sequential TRACK_STATUS before the first FETCH that this pipelines
1 change: 1 addition & 0 deletions rs/moq-net/src/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,7 @@ pub fn seeds() -> Vec<Seed> {
fetch_type,
range_filters: false,
fill_timeout: false,
properties_wanted: true,
};
fetch.encode_bytes(*version).ok()
}) else {
Expand Down
8 changes: 8 additions & 0 deletions rs/moq-net/src/ietf/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,14 @@ fn classify(type_id: u64, body: &Bytes, version: Version, namespaces: &Namespace
}
_ => Err(Error::UnexpectedMessage),
},
// TRACK_STATUS_OK and TRACK_STATUS_ERROR (v14 only): the one answer the request gets.
ietf::TrackStatusOk::ID_14 | ietf::TRACK_STATUS_ERROR_14 => match version {
Version::Draft14 => {
let id = decode_request_id(body, version)?;
Ok(Route::CloseStream(id))
}
_ => Err(Error::UnexpectedMessage),
},
// PublishOk (0x1E)
ietf::PublishOk::ID => {
let id = decode_response_request_id(body, version)?;
Expand Down
Loading
Loading