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
4 changes: 3 additions & 1 deletion quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ transport, benchmark tooling); worktrees isolate commits, not semantics.
- [Ingest source](/quest/m1/net-ingest-source.md) - an announce says whether the broadcast entered on this relay or a peer
- [Publisher clocks](/quest/m1/publisher-clock.md) - wire the shared clock through native and browser publisher restarts
- [Broadcast route](/quest/m1/js-broadcast-route.md) - JS Announce.Broadcast goes live on any claim matching its path, like Rust routed()
- [io_uring handshake flush](/quest/m1/uring-handshake-flush.md) - a dial resolves only once its last handshake flight is on the wire, never stranding the peer
- [CLI inspection](/quest/m1/cli-inspect/README.md) - `moq ls` lists what is live and `moq fetch` reads a group over MoQ, and a guide shows how to inspect a relay
- [JS caught up](/quest/m1/js-announce-caught-up.md) - @moq/net's announce consumer says when the initial set has landed, like Rust
- [Bindings caught up](/quest/m1/announce-live-bindings.md) - moq-ffi, libmoq, and every wrapper yield the same flat announce event, `Live` included
- [IETF announce count](/quest/m1/ietf-announce-count.md) - an opt-in moq-transport extension carries the replay count, so IETF announce consumers go live without a timer

- [Jitter clock](/quest/m1/jitter-flush-clock.md) - moq-mux: catalog jitter measures how far behind the media clock an encoder flushes, fed by encoders only, and never decreases
Expand All @@ -53,6 +53,8 @@ transport, benchmark tooling); worktrees isolate commits, not semantics.
- [Path patterns](/quest/m1/path-patterns.md) - one matcher for every predicate over broadcast paths: tokens, origins, interest
- [In-band auth](/quest/m1/auth/README.md) - a session tells its peer what it may publish and subscribe to, unions tokens presented in band, and fails loud on an out-of-scope publish
- [Stats retier](/quest/m1/stats-retier.md) - a re-checked tier retags a live session's stats in place instead of waiting for its next connection
- [Listener close](/quest/m1/listener-close.md) - closing a listener releases its UDP socket before returning, so a restart can rebind the port
- [Test ports](/quest/m1/tokio-test-ports.md) - moq-tokio tests bind QUIC and WebSocket on independent ephemeral ports, so a parallel run cannot collide
- [Decoded frame ownership](/quest/m1/decoded-frames.md) - retain moq-video Frames across bindings, with native views or CPU conversion as needed
- [C++ through moq-ffi](/quest/m1/cpp/README.md) - generated C++ over moq-ffi with futures and expected-style errors, shipped as a tarball, vcpkg, and Conan, and adopted by the OBS plugin
- [OBS native codecs](/quest/m1/obs-moq-video/README.md) - remove FFmpeg decoding dependencies, deliver GPU frames, and use native audio/video encoders
Expand Down
33 changes: 33 additions & 0 deletions quest/m1/announce-live-bindings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# [M] Bindings see when an announce consumer has caught up

## Goal

moq-ffi, libmoq, and every wrapper (`py`, `swift`, `kt`, `go`, `dart`) yield
the same flat announce event Rust does: `Announced`, `Updated`, or `Retracted`
carrying the announce, or `Live` once every route live at subscribe time has
been delivered. A binding app can list what is live and stop, with no timer.

## Plan

Mirror the Rust `announce::Event` from
[Caught up](/quest/m1/cli-inspect/caught-up.md) one to one:

- moq-ffi: `MoqAnnounceConsumer::next` returns `MoqAnnounceEvent`, a uniffi
enum with the four variants, replacing `MoqAnnounceUpdate::active()`. Stop
filtering `Live` in `rs/moq-ffi/src/origin.rs`.
- libmoq: the `on_announce` handle's `moq_announce_update.kind` gains a LIVE
value with no route fields, replacing the active flag. Stop skipping
`Event::Live` in `rs/libmoq/src/origin.rs`.
- Hand-written wrappers and `doc/lib/{py,swift,kt,go,dart,c}` follow, with a
test per binding that an empty origin still yields `Live`.

Public API: breaking in moq-ffi, libmoq's C ABI, and every wrapper, so it
retargets to `dev` with the Rust break. Wire: none.

## Required

- [Caught up](/quest/m1/cli-inspect/caught-up.md) - settles the event shape this mirrors

## Related

- [JS caught up](/quest/m1/js-announce-caught-up.md) - the same event in `@moq/net`
4 changes: 4 additions & 0 deletions quest/m1/auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ existing lite-06 ALPN.
- [Lite stream](/quest/m1/auth/lite.md) - both sides of a lite-06 session
exchange grants over AUTH streams, exposed as `Session::auth()`, and an
out-of-scope announce aborts the session
- [Interop grants](/quest/m1/auth/interop.md) - the interop matrix asserts
each lite-06 cell's grant and that a publish outside it fails loud
- [Unauthorized reset](/quest/m1/auth/unauthorized.md) - a subscription that
loses access resets with a dedicated UNAUTHORIZED stream code
- [Relay tokens](/quest/m1/auth/relay-refresh.md) - the relay verifies tokens
sent in band, unions their grants, and cancels only work that loses access
- [moq-transport](/quest/m1/auth/moq-transport.md) - the same exchange as a
Expand Down
3 changes: 3 additions & 0 deletions quest/m1/auth/bindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ mid-stream can be handed a new one without the plugin reconnecting.
async calls use, in `rs/libmoq/src/api.rs` and the session table;
regenerate `moq.h`, and update `cpp/obs/src` only if the plugin surfaces a
token field, otherwise leave it.
- Interop: the Python, Go, and C interop clients print their grant and join
the assertion [Interop grants](/quest/m1/auth/interop.md) adds for Rust
and JS.
- Wrappers: `py/moq-rs/moq/session.py`, `swift/Sources/Moq`,
`kt/.../Flows.kt` (a `Flow` over `grant_changed`), `go/wrapper/moq/session.go`
(context-cancellable like the rest), and `dart/moq/lib/moq.dart`. Kotlin
Expand Down
24 changes: 24 additions & 0 deletions quest/m1/auth/interop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# [S] Interop asserts the AUTH grant

## Goal

`just test interop --all` fails a lite-06 cell whose client did not receive
the grant its relay token implies, and a new negative round passes only when
a client publishing outside its grant fails loud with `Unauthorized`. Today
the matrix checks media alone, so a malformed AUTH_OK that leaves a session
with no grant passes silently.

## Plan

The Rust and JS interop clients each print the grant they received in one
parseable line; `test/interop/interop.sh`
compares it against the token the cell minted. The negative round mints a
token that excludes the published path and expects the publisher's session
to close with `Unauthorized` naming the path, and the subscriber to see
nothing. Cells below lite-06 skip both checks, as do binding clients until
[Bindings](/quest/m1/auth/bindings.md) gives them a grant to print; that
quest adds them to the same assertion. No public API or wire change.

## Required

- [Lite stream](/quest/m1/auth/lite.md) - the AUTH exchange being asserted
12 changes: 8 additions & 4 deletions quest/m1/auth/relay-refresh.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,17 @@ expiry, an expiry that leaves the union intact ends only that token, and
- Expiry: the deadline today is the admitted lease's `closed()` inside one
`tokio::select!` arm. Select on one lease per token in the set instead, and on any firing recompute the union without it: unchanged
means `AUTH_ERROR { Expired }` on that token's stream and the session
continues, shrunk means `session.abort(Unauthorized)` as today. A token
continues, shrunk narrows the origin and resets each stream that lost
access with `UNAUTHORIZED`. A token
withdrawn by the client (its stream closed or reset) recomputes the union
the same way with nothing written back, since the stream is gone: unchanged
means the session continues, shrunk means abort. Each lease revalidates on
means the session continues, shrunk narrows the same way. Each lease revalidates on
Comment thread
kixelated marked this conversation as resolved.
its own cadence, and every re-check that changes the grant, a lower
`expires` or a different `publish` or `subscribe`, writes the complete
replacement grant as an update AUTH_OK on that token's stream and
recomputes the union from the latest grant of every token, so the client
can present a replacement in time and a narrowed token never keeps its old
scope; a shrunk union aborts as above until Origin scopes resizes.
scope; a shrunk union narrows as above.
- Refusals map as connect-time ones do and never expose a status: every
refusal `Client::attach` returns for a new token, a `403`, an empty or
invalid grant, an unparseable body, or a `5xx`, is
Expand All @@ -71,7 +72,8 @@ expiry, an expiry that leaves the union intact ends only that token, and
an expired or invalid token is refused and nothing changes; a token with a
different root is refused naming it; a token adding a prefix rebuilds the
origin scope and the prefix is now solicited; withdrawing the only token
covering a prefix closes the session; an mTLS session refuses a token; a
covering a prefix resets that prefix's streams with `UNAUTHORIZED` and
the session stays connected; an mTLS session refuses a token; a
proxy-mode outage on re-check keeps the token.

Additive.
Expand All @@ -82,3 +84,5 @@ Additive.
- [Pattern interest](/quest/m1/path-patterns.md) - AUTH can represent the complete grants relay revalidation returns
- [Lite stream](/quest/m1/auth/lite.md) - supplies the AUTH stream and
`auth::Request` this consumes
- [Unauthorized reset](/quest/m1/auth/unauthorized.md) - the code this
relay's revocations reset with
Comment thread
coderabbitai[bot] marked this conversation as resolved.
25 changes: 25 additions & 0 deletions quest/m1/auth/unauthorized.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# [S] A subscription that loses access resets with UNAUTHORIZED

## Goal

When a grant shrinks and a subscription, fetch, or announce loses access,
its stream resets with a dedicated `UNAUTHORIZED` stream code, so the peer
tells revocation apart from a session closing. Today it resets with
`0x3 SESSION_CLOSED` (`StreamError::Session(Unauthorized)`).

## Plan

Assign `0x3A UNAUTHORIZED` in the lite draft's stream error table, the next
code in the application block after `TIMESTAMP_MISMATCH`, on lite-06: a
published lite-06 peer already maps an unknown stream code to a generic
error, so the addition is compatible. Add the matching `StreamError`
variant (the enum is non-exhaustive) in Rust and JS, send it from every
revocation path the lite stream added, and cover it in the Rust and JS
Comment thread
kixelated marked this conversation as resolved.
tests. Map it to the existing `Unauthorized` protocol kind in
`stream_kind` in `rs/moq-ffi/src/error.rs` and `rs/libmoq/src/error.rs`,
whose wildcard arms would otherwise report `Unknown`, with a test for each.
Update `drafts/draft-lcurley-moq-lite.md` and run `just drafts check`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include the concept documentation in this wire quest

This quest adds a new moq-net stream error code, but its synchronized work stops at Rust, JS, and the draft. If the quest is executed as scoped, the user-facing protocol documentation under doc/concept will remain stale even though the repository requires every rs/moq-net wire/API change to update it. Add the relevant concept-page update to the plan. (Written by GPT-5.6 Sol)

AGENTS.md reference: AGENTS.md:L95-L99

Useful? React with 👍 / 👎.


## Required

- [Lite stream](/quest/m1/auth/lite.md) - the revocation paths that send it
4 changes: 3 additions & 1 deletion quest/m1/js-announce-caught-up.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ broadcasts" instead of a spinner that never resolves.

## Plan

Mirror the Rust shape, name, and marker-less fallback. Test the same cases
Mirror the Rust shape, name, and marker-less fallback: one flat event,
`Announced`, `Updated`, `Retracted` (each carrying the announce), or `Live`,
replacing the update's `kind` field. Test the same cases
in JS. Public API: the announce consumer's yield type changes, so it lands
with the Rust break. Wire: none.

Expand Down
20 changes: 20 additions & 0 deletions quest/m1/listener-close.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# [S] Closing a listener releases its socket before returning

## Goal

Closing a moq-tokio server, and moq-ffi's `Listener::close` on top of it,
returns only after the UDP socket is released, so a restart can rebind the
same port at once. Go's `TestReconnectAcrossRelayRestart`
(`go/wrapper/reconnect_test.go`) fails on main today because the rebind
races the old socket.

## Plan

`Server::shutdown` (`rs/moq-tokio/src/server.rs`) calls `noq.close()` and
sleeps 100 ms without waiting for the endpoint driver to drop the socket.
Await the driver's exit instead and delete the sleep; moq-ffi's
`Listener::close` already documents a synchronous release, so make it true
rather than change the doc. Check the io_uring and WebSocket listeners for
the same gap. Add a Rust test that closes a server and rebinds its port
immediately; the Go test becomes the cross-language regression. No wire
change; the only API change is that close takes as long as the release.
17 changes: 17 additions & 0 deletions quest/m1/tokio-test-ports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# [XS] moq-tokio tests bind their ports independently

## Goal

moq-tokio's integration tests never fail on a port another process or
parallel test holds. `websocket_forbidden_does_not_end_a_quic_connect`
(`rs/moq-tokio/tests/broadcast.rs`) fails about 1 in 15 local runs today.

## Plan

`test_server()` takes an ephemeral UDP port and then binds TCP on the same
number, which was never reserved on TCP. Bind the QUIC and WebSocket
listeners each on `:0` and hand the test client the WebSocket port
explicitly, adding a client config override for the fallback URL's port if
none exists (test-only if possible; otherwise note it as a public API
addition). No retry: this replaces the bounded bind retry
[#4055](https://github.com/moq-dev/moq/pull/4055) added. No wire change.
34 changes: 0 additions & 34 deletions quest/m1/uring-handshake-flush.md

This file was deleted.

1 change: 1 addition & 0 deletions quest/m2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ upstream release waits in [m4](/quest/m4/README.md).
- [Audio loss recovery](/quest/m2/audio-loss-recovery.md) - prove a useful Opus recovery policy before exposing another option
- [Opus implementation](/quest/m2/audio-opus-backend.md) - compare current codec quality, CPU, and optional build costs
- [Latency ledger](/quest/m2/latency-ledger.md) - a session reports where its end-to-end audio delay went, stage by stage
- [JS discontinuity](/quest/m2/js-discontinuity.md) - JS names its timeline break `discontinuity()` like Rust, so `cut` means the same group close in both
- [Media Foundation decode](/quest/m2/audio-decode-mediafoundation.md) - Windows decodes HE-AAC, multichannel AAC, and what else the MFTs offer
- [Media Foundation encode](/quest/m2/audio-encode-mediafoundation.md) - Windows encodes AAC-LC
- [MediaCodec decode](/quest/m2/audio-decode-mediacodec.md) - Android decodes HE-AAC, multichannel AAC, and what else the device offers
Expand Down
24 changes: 24 additions & 0 deletions quest/m2/js-discontinuity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# [S] JS names the break discontinuity()

## Goal

`@moq/hang`'s container producer spells its two operations the way Rust
`moq_mux::container::Producer` does: `cut(end?)` closes the current group,
and `discontinuity()` closes it and writes the empty marker group that tells
subscribers to re-anchor. Today JS's public `cut()` writes the marker, so the
same name means a routine group close in Rust and a timeline break in JS.

## Plan

[#4045](https://github.com/moq-dev/moq/pull/4045) deletes
`quest/m1/js-publish-discontinuity.md`, since #3982 already put the marker
in `cut()`; this rename is the only remaining JS work.

In `js/hang/src/container/legacy.ts`, rename the public `cut(end?)` to
`discontinuity()` (taking the same optional end) and keep the routine close
private until a caller needs it public. Move `js/publish/src/video/encoder.ts`
and any other caller over, and keep data tracks skipping a sequence the way
Comment thread
kixelated marked this conversation as resolved.
Rust's `discontinuity()` does if a JS data-track caller appears. Rust is
untouched.

Public API: breaking in published `@moq/hang`, so it targets `dev`. Wire: none.
Loading