Skip to content

feat(net): mark the end of the initial announce set over moq-transport - #2826

Open
kixelated wants to merge 1 commit into
mainfrom
claude/ietf-extension-2dd0e1
Open

feat(net): mark the end of the initial announce set over moq-transport#2826
kixelated wants to merge 1 commit into
mainfrom
claude/ietf-extension-2dd0e1

Conversation

@kixelated

@kixelated kixelated commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Closes #2789.

Summary

  • moq-transport marks nothing between the namespaces a publisher already had and the ones that showed up a moment later: SUBSCRIBE_NAMESPACE is answered with REQUEST_OK and then a NAMESPACE per match, all the same message on the same stream. A subscriber can see that a namespace is present but never that it is absent, which is what origin::Consumer::request_broadcast needs before falling back to a dynamic handler or Unroutable. moq-lite has had that boundary since lite-05 (ANNOUNCE_OK.Active Count).
  • New draft drafts/draft-lcurley-moq-namespace-count.md: a NAMESPACE_COUNT Setup Option asking for a NAMESPACE_COUNT Message Parameter on each SUBSCRIBE_NAMESPACE_OK. That many NAMESPACE messages are the initial set; everything after is a live update. 0 is a real answer (the prefix is empty), absent means the peer doesn't implement it.
  • Two findings from moq-transport-19 pinned the shape, and both rule out the alternative the issue floated (an explicit end-of-set marker message):
    • Unknown Message Parameters are fatal ("An endpoint that receives an unknown Message Parameter MUST close the session with PROTOCOL_VIOLATION... MUST be defined in the negotiated version or negotiated via Setup Options", §10.2). So the parameter has to be negotiated, the same way MoQ Cluster negotiates HOP_PATH via RELAY_HOPS, and we only send it to a peer that asked.
    • There is no IANA registry for control message types (§15 registers Setup Options, Message Parameters, Properties, error codes... and no messages). A new message would have no codepoint path, and would need negotiation anyway.
  • The draft also covers the REQUEST_UPDATE_OK that accepts a change of Track Namespace Prefix (§10.9.2), since that starts a fresh set on the same stream and needs its own boundary. That clause is spec-only today: we open one stream per prefix and never send or accept a prefix update.
  • Scoped to versions that have NAMESPACE: draft-14/15 answer with PUBLISH_NAMESPACE requests on streams of their own, so there is nothing on the response stream to count, and draft-14's SUBSCRIBE_NAMESPACE_OK carries no parameters at all. We neither ask nor answer there.

Implementation

  • rs/moq-net/src/ietf/namespace_count.rs: the option/parameter codepoints and the SETUP round trip, shaped like ietf::solicit.
  • Publisher: run_subscribe_namespace_stream now waits for the peer's SETUP before the response (it decides whether the parameter goes on it), drains the origin synchronously into the initial set, and writes the REQUEST_OK plus its counted NAMESPACE messages as one buffer. Selection runs once, during the drain, so the count and the messages can't disagree; a route that moves after that is a live update. Split-horizon-filtered namespaces are not counted, since they are never sent.
  • Subscriber: reads the count off the response and releases the connect gate once that many NAMESPACE messages have arrived (immediately for 0 or for a peer that reports none). Reflected announcements we drop locally still count: the peer doesn't know we dropped them.
  • Connecting moved from lite/ to connecting.rs and ietf::start now returns it, so connect() blocks on the initial announce set over moq-transport exactly as it already does over moq-lite. The wait is bounded by the existing connect deadline (moq_native::ClientConfig::timeout, 30s default), which the draft requires.
  • js/net mirrors the wire: declares the option, sends the count as a publisher, parses it as a subscriber. It does not gate anything on it yet, because js/net has no connect-gate to hang it on (its lite subscriber ignores AnnounceOk.active for the same reason). Wire-compatible in both directions today; a JS-side boundary is a follow-up.

Public API changes

None. ietf and lite are private modules; ietf::SessionStart, connecting::{Connecting, ConnectingProducer}, and ietf::peer::Peer::namespace_count are all pub(crate) or crate-internal. The only exported change is a doc-comment update on origin::Consumer::announced_broadcast.

Cross-package sync

rs/moq-net wire → js/net (done), drafts/ (the new draft), doc/ (a bullet in doc/concept/standard/interop.md). No hang, moq-ffi, or CLI surface is touched.

Test plan

  • just check / just test.
  • New Rust tests: parameter round trip incl. Some(0) vs None (ietf::request), SETUP negotiation per version (ietf::namespace_count), publisher response counting (initial set, empty set, peer that didn't ask, split-horizon-filtered namespace excluded), subscriber connect gating (counted set completes, short set never completes, empty/absent complete immediately).
  • New JS test: js/net/src/ietf/namespace_count.test.ts (SETUP negotiation per version, 0n vs absent on the response).
  • nix develop --command just drafts check parses the new draft.

Not run: the cross-language interop matrix (just test smoke-full).

(Written by Opus 5)

moq-transport marks nothing between the namespaces a publisher already had
and the ones that showed up a moment later, so a subscriber can see that a
namespace is present but never that it is absent. moq-lite has had that
boundary since lite-05 (ANNOUNCE_OK.Active Count).

Adds the MoQ Namespace Count extension: a NAMESPACE_COUNT Setup Option asking
for a NAMESPACE_COUNT parameter on the REQUEST_OK that opens a namespace
response set. Unknown Message Parameters are fatal in moq-transport, so the
parameter is negotiated rather than sent unasked, and there is no registry for
control message types, so an explicit end-of-set message was not an option.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@kixelated, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 29 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7b1d8d73-391a-4a0c-a2b9-e4537078f868

📥 Commits

Reviewing files that changed from the base of the PR and between f91e3bb and 909eb52.

📒 Files selected for processing (28)
  • doc/concept/standard/interop.md
  • drafts/draft-lcurley-moq-namespace-count.md
  • js/net/src/connection/accept.ts
  • js/net/src/connection/connect.ts
  • js/net/src/connection/handshake.ts
  • js/net/src/ietf/connection.ts
  • js/net/src/ietf/index.ts
  • js/net/src/ietf/namespace_count.test.ts
  • js/net/src/ietf/namespace_count.ts
  • js/net/src/ietf/parameters.ts
  • js/net/src/ietf/publisher.ts
  • js/net/src/ietf/subscriber.ts
  • rs/moq-net/src/client.rs
  • rs/moq-net/src/connecting.rs
  • rs/moq-net/src/ietf/mod.rs
  • rs/moq-net/src/ietf/namespace_count.rs
  • rs/moq-net/src/ietf/parameters.rs
  • rs/moq-net/src/ietf/peer.rs
  • rs/moq-net/src/ietf/publisher.rs
  • rs/moq-net/src/ietf/request.rs
  • rs/moq-net/src/ietf/session.rs
  • rs/moq-net/src/ietf/subscriber.rs
  • rs/moq-net/src/lib.rs
  • rs/moq-net/src/lite/mod.rs
  • rs/moq-net/src/lite/session.rs
  • rs/moq-net/src/lite/subscriber.rs
  • rs/moq-net/src/model/origin.rs
  • rs/moq-net/src/server.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 909eb52b80

ℹ️ 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".

Comment on lines +1378 to +1379
let initial = self.initial_namespaces(&mut announced, &prefix, &mut ns);
let namespace_count = declared.namespace_count.then_some(initial.len() as u64);

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 Avoid reporting an empty set when solicitation is disabled

When a peer negotiates Namespace Count but omits or disables the independent Solicit option, the origin above is replaced with origin.empty(), so this reports Some(0) even if the publisher has live namespaces being sent through unsolicited PUBLISH_NAMESPACE requests. A subscriber following the new draft may therefore conclude that the publisher does not have a namespace while its advertisement is still in flight. The mirrored JS implementation has the same behavior; either the count must be omitted in this mode or the extension must require solicitation before treating the response as a complete snapshot.

Useful? React with 👍 / 👎.

Comment thread rs/moq-net/src/client.rs
Comment on lines +182 to +183
let (session, mut driver) = Session::new(session, v, None, start.driver);
driver.wait_ready(|waiter| start.connecting.poll_ready(waiter)).await;

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 Bound the namespace-count readiness wait

For direct moq_net::Client callers, a peer can return NAMESPACE_COUNT = N, send fewer than N entries, and leave the response stream and session open; this new wait_ready then prevents connect() from ever returning. The timeout mentioned in the extension comments exists only in the higher-level moq_native wrapper, while this public generic client has no deadline, contrary to the new draft's requirement that subscribers not wait indefinitely.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

moq-transport (and moq-lite-03/04) have no end-of-initial-set marker for announce interest

1 participant