docs(audio-rooms): full moq-lite wire spec + IETF gap call-outs
Background research turned up the complete moq-lite (Lite-03) wire
format from kixelated/moq-rs and @moq/lite v0.1.7. Folded the spec
into nestsClient/plans/2026-04-26-moq-lite-gap.md as a phase-5
implementation plan:
- ALPN ("moq-lite-03"); no SETUP/control message in Lite-03 (the WT
handshake IS the handshake)
- Per-request bidi streams keyed by ControlType varint (Announce=1,
Subscribe=2, Fetch=3, Probe=4)
- AnnouncePlease(prefix) / Announce(status, suffix, hops) shape
- Subscribe with priority (raw u8), ordered, maxLatency (ms),
startGroup/endGroup (off-by-one None-encoded), reply Ok/Drop
- Group = uni stream with (DataType=0, subscribeId, sequence)
header followed by varint-length frames until QUIC FIN
- No datagrams; no per-frame envelope beyond size
- Mandatory path normalisation; FIN-as-unsubscribe; RESET_STREAM
for errors
Phase-5a..e implementation plan included (~1 week scope).
Doc + KDoc updates so the IETF MoQ-transport code is correctly
labelled and the moq-lite gap is discoverable from every entry point:
- .claude/CLAUDE.md project description and architecture diagram
- nestsClient/plans/2026-04-26-audio-rooms-completion.md status block
- MoqSession.kt, MoqMessage.kt, MoqObject.kt, MoqCodec.kt KDoc — flag
these as "IETF draft-ietf-moq-transport-17, NOT moq-lite", with
pointers to the gap doc
- NestsConnect.kt — note that step 3 of the listener handshake
(SETUP) does NOT match nostrnests's relay framing
This commit is contained in:
@@ -43,6 +43,13 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
* bearer token.
|
||||
* 3. Run the MoQ SETUP handshake.
|
||||
*
|
||||
* Note: step 3 runs the IETF `draft-ietf-moq-transport-17` SETUP, which is
|
||||
* NOT what nostrnests's relay (moq-lite) expects. Steps 1 and 2 are
|
||||
* already on the moq-rs wire shape (path = `/<namespace>`, JWT in
|
||||
* `?jwt=`). The MoQ framing layer needs a moq-lite codec swap before
|
||||
* end-to-end interop works — see
|
||||
* `nestsClient/plans/2026-04-26-moq-lite-gap.md`.
|
||||
*
|
||||
* The returned [NestsListener] is in state [NestsListenerState.Connected];
|
||||
* if any step fails, the listener is returned in
|
||||
* [NestsListenerState.Failed] with the underlying cause attached and the
|
||||
|
||||
@@ -24,7 +24,8 @@ import com.vitorpamplona.nestsclient.moq.MoqCodec.encode
|
||||
import com.vitorpamplona.quic.Varint
|
||||
|
||||
/**
|
||||
* Encode/decode MoQ control-stream messages per draft-ietf-moq-transport.
|
||||
* Encode/decode IETF `draft-ietf-moq-transport-17` control-stream
|
||||
* messages.
|
||||
*
|
||||
* Wire format for every control message:
|
||||
*
|
||||
@@ -32,6 +33,10 @@ import com.vitorpamplona.quic.Varint
|
||||
*
|
||||
* The payload layout is per-message. Payload encoders here produce only the
|
||||
* payload; [encode] wraps it with type+length for on-the-wire use.
|
||||
*
|
||||
* Wire-protocol scope: IETF MoQ-transport, NOT moq-lite. nostrnests's
|
||||
* reference relay uses moq-lite — see
|
||||
* `nestsClient/plans/2026-04-26-moq-lite-gap.md`.
|
||||
*/
|
||||
object MoqCodec {
|
||||
/**
|
||||
|
||||
@@ -21,17 +21,19 @@
|
||||
package com.vitorpamplona.nestsclient.moq
|
||||
|
||||
/**
|
||||
* Subset of the MoQ-transport control-plane messages needed for a listener
|
||||
* talking to a nests server. Per draft-ietf-moq-transport, control messages
|
||||
* on the control stream share the wire layout:
|
||||
* Subset of the **IETF `draft-ietf-moq-transport-17`** control-plane
|
||||
* messages. Wire layout per the IETF draft: control messages on the
|
||||
* single bidi share
|
||||
*
|
||||
* message_type (varint) | message_length (varint) | payload...
|
||||
*
|
||||
* Listener-side messages (CLIENT/SERVER_SETUP, SUBSCRIBE, SUBSCRIBE_OK /
|
||||
* SUBSCRIBE_ERROR, UNSUBSCRIBE) are implemented. Publisher-side messages
|
||||
* (ANNOUNCE / ANNOUNCE_OK / SUBSCRIBE-receiving / SUBSCRIBE_DONE) are not
|
||||
* yet implemented; see `nestsClient/plans/2026-04-26-audio-rooms-completion.md`
|
||||
* (Phase M5).
|
||||
* Wire-protocol scope: this is the IETF MoQ-transport flavour. The
|
||||
* nostrnests reference relay speaks **moq-lite** (kixelated's variant)
|
||||
* which uses a wire-incompatible shape — no SETUP message, ControlType
|
||||
* discriminator on each fresh bidi stream, single-string broadcast +
|
||||
* track names rather than tuples. See
|
||||
* `nestsClient/plans/2026-04-26-moq-lite-gap.md` for the full spec and
|
||||
* the planned moq-lite parallel codec.
|
||||
*/
|
||||
sealed class MoqMessage {
|
||||
abstract val type: MoqMessageType
|
||||
|
||||
@@ -21,20 +21,25 @@
|
||||
package com.vitorpamplona.nestsclient.moq
|
||||
|
||||
/**
|
||||
* A single MoQ object — a protocol-level unit of publisher-produced data
|
||||
* (one Opus frame for nests audio) identified by its (track, group, object)
|
||||
* coordinates and carrying application bytes.
|
||||
* A single object in **IETF `draft-ietf-moq-transport-17`** — a
|
||||
* publisher-produced unit identified by `(track_alias, group_id,
|
||||
* object_id)` coordinates with a `publisher_priority` and a `status`
|
||||
* varint, carrying app bytes.
|
||||
*
|
||||
* MoQ objects are delivered in three ways per draft-ietf-moq-transport:
|
||||
* Per the IETF draft, objects are delivered three ways:
|
||||
*
|
||||
* 1. OBJECT_DATAGRAM — one object per QUIC datagram. Lowest latency, no
|
||||
* retransmits. Used by nests for real-time audio.
|
||||
* retransmits.
|
||||
* 2. STREAM_HEADER_SUBGROUP — multiple objects per uni stream, reliable.
|
||||
* 3. FETCH_HEADER — historical objects over a bidi stream.
|
||||
*
|
||||
* Today the listener path implements only (1) — OBJECT_DATAGRAM — which is
|
||||
* what nests uses for live audio. (2) and (3) are reserved for future
|
||||
* stream-delivered media; see the audio-rooms completion plan.
|
||||
* This shape is **not** what nostrnests's relay (moq-lite) uses on the
|
||||
* wire. moq-lite delivers media as *frames within groups within uni
|
||||
* streams*: one uni stream per group, each with `(subscribe_id, group_seq)`
|
||||
* header and then a sequence of `(size_varint, payload)` frames; no
|
||||
* datagram path, no `track_alias`, no `publisher_priority`/`status`
|
||||
* envelope. See `nestsClient/plans/2026-04-26-moq-lite-gap.md` §5 for
|
||||
* the moq-lite frame layout.
|
||||
*/
|
||||
data class MoqObject(
|
||||
val trackAlias: Long,
|
||||
|
||||
@@ -56,7 +56,17 @@ object MoqVersion {
|
||||
}
|
||||
|
||||
/**
|
||||
* Session wrapper over a [WebTransportSession] that speaks MoQ-transport.
|
||||
* Session wrapper over a [WebTransportSession] that speaks IETF
|
||||
* `draft-ietf-moq-transport-17` (CLIENT_SETUP / SERVER_SETUP, namespace
|
||||
* tuples, OBJECT_DATAGRAM with `track_alias`).
|
||||
*
|
||||
* **Wire-protocol scope:** this is the IETF MoQ-transport flavour, NOT
|
||||
* kixelated's moq-lite. The two are wire-incompatible (single-string
|
||||
* paths vs. tuple namespaces, different ANNOUNCE/SUBSCRIBE shape).
|
||||
* nostrnests's reference relay (`kixelated/moq-rs`) speaks moq-lite, so
|
||||
* this session cannot exchange a single MoQ message with the real
|
||||
* nostrnests stack. See `nestsClient/plans/2026-04-26-moq-lite-gap.md`
|
||||
* for the gap and the planned moq-lite parallel codec.
|
||||
*
|
||||
* Lifecycle:
|
||||
* 1. [client] / [server] attaches to a transport. No traffic yet.
|
||||
|
||||
Reference in New Issue
Block a user