diff --git a/nestsClient/src/commonMain/kotlin/com/vitorpamplona/nestsclient/moq/lite/MoqLiteAlpn.kt b/nestsClient/src/commonMain/kotlin/com/vitorpamplona/nestsclient/moq/lite/MoqLiteAlpn.kt new file mode 100644 index 000000000..2c3186ef6 --- /dev/null +++ b/nestsClient/src/commonMain/kotlin/com/vitorpamplona/nestsclient/moq/lite/MoqLiteAlpn.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.nestsclient.moq.lite + +/** + * moq-lite ALPN strings. ONLY [LITE_03] is wire-compatible with the + * codec in [MoqLiteCodec]; the other constants are kept for + * documentation and future codec upgrades. + * + * Subscribe / Group framing is identical across Lite-03 and Lite-04, + * but Lite-04 reshapes Announce.hops into an `OriginList` + * (`kixelated/moq` commit 45db108, "moq-lite/moq-relay: hop-based + * clustering"), adds an `exclude_hop` field to AnnounceInterest, and + * adds an `rtt` field to Probe. A relay that picks Lite-04 with our + * Lite-03 codec on the wire desyncs on the first Announce exchange. + * Don't add [LITE_04] to `wt-available-protocols` until + * [MoqLiteCodec] is version-aware and [MoqLiteAnnounce.hops] is a + * list rather than a single varint. + * + * `"moql"` is the legacy combined ALPN that requires an in-band SETUP + * exchange — kept here for completeness; not advertised by the + * factory. + * + * Source: `kixelated/moq/rs/moq-lite/src/lite/version.rs`, + * `kixelated/moq/rs/moq-lite/src/lite/announce.rs:11-105`, + * `kixelated/moq/rs/moq-lite/src/lite/probe.rs:9-55`. + */ +object MoqLiteAlpn { + const val LITE_03: String = "moq-lite-03" + + /** + * `moq-lite-04` ALPN string. Wire-incompatible with [MoqLiteCodec] + * today — see the object kdoc for the codec diff. Defined here so + * a future patch that lands version-aware Announce / Probe codecs + * can drop it into the [QuicWebTransportFactory] sub-protocol list + * without re-deriving the constant. + */ + const val LITE_04: String = "moq-lite-04" + const val LEGACY: String = "moql" +} diff --git a/nestsClient/src/commonMain/kotlin/com/vitorpamplona/nestsclient/moq/lite/MoqLiteControlCodes.kt b/nestsClient/src/commonMain/kotlin/com/vitorpamplona/nestsclient/moq/lite/MoqLiteControlCodes.kt new file mode 100644 index 000000000..2d55a2c81 --- /dev/null +++ b/nestsClient/src/commonMain/kotlin/com/vitorpamplona/nestsclient/moq/lite/MoqLiteControlCodes.kt @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.nestsclient.moq.lite + +// Wire-format discriminators for moq-lite control flow. +// +// The four enums below share a common job: they're varint / byte tags +// the wire-format parser (`MoqLiteCodec`) reads at message boundaries to +// decide which body to decode next. None of them carry payload data +// themselves; the data classes that DO live in `MoqLiteMessages.kt`. +// +// Source: `kixelated/moq/rs/moq-lite/src/lite/{stream,announce,subscribe}.rs`. + +/** + * ControlType varint discriminator written as the first datum on every + * client-initiated bidi stream. Selects which message body the peer + * should expect to read next. + * + * Source: `rs/moq-lite/src/lite/stream.rs:7-15`. + */ +enum class MoqLiteControlType( + val code: Long, +) { + /** Lite-01/02 only — unused on Lite-03. Reserved here for completeness. */ + Session(0L), + Announce(1L), + Subscribe(2L), + Fetch(3L), + Probe(4L), + + /** + * Graceful relay-shutdown signal. moq-rs's `Publisher::run` accepts + * `ControlType::Goaway = 5` (`rs/moq-lite/src/lite/publisher.rs`) + * to migrate a publisher to a different relay node. We don't act + * on it today — recognising the type code prevents + * [MoqLiteSession.handleInboundBidi] from silently FINing the + * bidi as an unknown control type, which would lose the relay's + * shutdown notification. Wire body decoding is left as a follow-up. + */ + Goaway(5L), + ; + + companion object { + private val byCode = entries.associateBy { it.code } + + fun fromCode(code: Long): MoqLiteControlType? = byCode[code] + } +} + +/** + * DataType varint written as the first byte of every uni stream that + * carries payload. Lite-03 currently uses `Group=0` only. + * + * Source: `rs/moq-lite/src/lite/stream.rs:32-36`. + */ +enum class MoqLiteDataType( + val code: Long, +) { + Group(0L), + ; + + companion object { + private val byCode = entries.associateBy { it.code } + + fun fromCode(code: Long): MoqLiteDataType? = byCode[code] + } +} + +/** + * Status byte at the head of an [MoqLiteAnnounce] payload. `Active=1` + * is sent on first publish; `Ended=0` on explicit unannounce. Disconnect + * is NOT signalled with `Ended` — the bidi just closes. + * + * Source: `rs/moq-lite/src/lite/announce.rs:84-90`. + */ +enum class MoqLiteAnnounceStatus( + val code: Int, +) { + Ended(0), + Active(1), + ; + + companion object { + fun fromCode(code: Int): MoqLiteAnnounceStatus? = + when (code) { + 0 -> Ended + 1 -> Active + else -> null + } + } +} + +/** + * Type discriminator at the head of a SubscribeResponse on the response + * side of a Subscribe bidi. Wire layout (Lite-03+): + * + * type varint (0 = Ok, 1 = Drop) + * body size-prefixed bytes + * + * The type sits OUTSIDE the body size prefix — see + * `rs/moq-lite/src/lite/subscribe.rs::SubscribeResponse::encode` (the + * `_` arm covers Lite03+). Earlier drafts wrapped type+body in one + * outer size prefix; Lite03 split them. + */ +enum class MoqLiteSubscribeResponseType( + val code: Long, +) { + Ok(0L), + Drop(1L), + ; + + companion object { + private val byCode = entries.associateBy { it.code } + + fun fromCode(code: Long): MoqLiteSubscribeResponseType? = byCode[code] + } +} diff --git a/nestsClient/src/commonMain/kotlin/com/vitorpamplona/nestsclient/moq/lite/MoqLiteMessages.kt b/nestsClient/src/commonMain/kotlin/com/vitorpamplona/nestsclient/moq/lite/MoqLiteMessages.kt index 66e75df97..48ff2f55d 100644 --- a/nestsClient/src/commonMain/kotlin/com/vitorpamplona/nestsclient/moq/lite/MoqLiteMessages.kt +++ b/nestsClient/src/commonMain/kotlin/com/vitorpamplona/nestsclient/moq/lite/MoqLiteMessages.kt @@ -20,147 +20,13 @@ */ package com.vitorpamplona.nestsclient.moq.lite -/** - * moq-lite ALPN strings. ONLY [LITE_03] is wire-compatible with the - * codec in [MoqLiteCodec]; the other constants are kept for - * documentation and future codec upgrades. - * - * Subscribe / Group framing is identical across Lite-03 and Lite-04, - * but Lite-04 reshapes Announce.hops into an `OriginList` - * (`kixelated/moq` commit 45db108, "moq-lite/moq-relay: hop-based - * clustering"), adds an `exclude_hop` field to AnnounceInterest, and - * adds an `rtt` field to Probe. A relay that picks Lite-04 with our - * Lite-03 codec on the wire desyncs on the first Announce exchange. - * Don't add [LITE_04] to `wt-available-protocols` until - * [MoqLiteCodec] is version-aware and [MoqLiteAnnounce.hops] is a - * list rather than a single varint. - * - * `"moql"` is the legacy combined ALPN that requires an in-band SETUP - * exchange — kept here for completeness; not advertised by the - * factory. - * - * Source: `kixelated/moq/rs/moq-lite/src/lite/version.rs`, - * `kixelated/moq/rs/moq-lite/src/lite/announce.rs:11-105`, - * `kixelated/moq/rs/moq-lite/src/lite/probe.rs:9-55`. - */ -object MoqLiteAlpn { - const val LITE_03: String = "moq-lite-03" - - /** - * `moq-lite-04` ALPN string. Wire-incompatible with [MoqLiteCodec] - * today — see the object kdoc for the codec diff. Defined here so - * a future patch that lands version-aware Announce / Probe codecs - * can drop it into the [QuicWebTransportFactory] sub-protocol list - * without re-deriving the constant. - */ - const val LITE_04: String = "moq-lite-04" - const val LEGACY: String = "moql" -} - -/** - * ControlType varint discriminator written as the first datum on every - * client-initiated bidi stream. Selects which message body the peer - * should expect to read next. - * - * Source: `rs/moq-lite/src/lite/stream.rs:7-15`. - */ -enum class MoqLiteControlType( - val code: Long, -) { - /** Lite-01/02 only — unused on Lite-03. Reserved here for completeness. */ - Session(0L), - Announce(1L), - Subscribe(2L), - Fetch(3L), - Probe(4L), - - /** - * Graceful relay-shutdown signal. moq-rs's `Publisher::run` accepts - * `ControlType::Goaway = 5` (`rs/moq-lite/src/lite/publisher.rs`) - * to migrate a publisher to a different relay node. We don't act - * on it today — recognising the type code prevents - * [MoqLiteSession.handleInboundBidi] from silently FINing the - * bidi as an unknown control type, which would lose the relay's - * shutdown notification. Wire body decoding is left as a follow-up. - */ - Goaway(5L), - ; - - companion object { - private val byCode = entries.associateBy { it.code } - - fun fromCode(code: Long): MoqLiteControlType? = byCode[code] - } -} - -/** - * DataType varint written as the first byte of every uni stream that - * carries payload. Lite-03 currently uses `Group=0` only. - * - * Source: `rs/moq-lite/src/lite/stream.rs:32-36`. - */ -enum class MoqLiteDataType( - val code: Long, -) { - Group(0L), - ; - - companion object { - private val byCode = entries.associateBy { it.code } - - fun fromCode(code: Long): MoqLiteDataType? = byCode[code] - } -} - -/** - * Status byte at the head of an [MoqLiteAnnounce] payload. `Active=1` - * is sent on first publish; `Ended=0` on explicit unannounce. Disconnect - * is NOT signalled with `Ended` — the bidi just closes. - * - * Source: `rs/moq-lite/src/lite/announce.rs:84-90`. - */ -enum class MoqLiteAnnounceStatus( - val code: Int, -) { - Ended(0), - Active(1), - ; - - companion object { - fun fromCode(code: Int): MoqLiteAnnounceStatus? = - when (code) { - 0 -> Ended - 1 -> Active - else -> null - } - } -} - -/** - * Type discriminator at the head of a SubscribeResponse on the response - * side of a Subscribe bidi. Wire layout (Lite-03+): - * - * type varint (0 = Ok, 1 = Drop) - * body size-prefixed bytes - * - * The type sits OUTSIDE the body size prefix — see - * `rs/moq-lite/src/lite/subscribe.rs::SubscribeResponse::encode` (the - * `_` arm covers Lite03+). Earlier drafts wrapped type+body in one - * outer size prefix; Lite03 split them. - */ -enum class MoqLiteSubscribeResponseType( - val code: Long, -) { - Ok(0L), - Drop(1L), - ; - - companion object { - private val byCode = entries.associateBy { it.code } - - fun fromCode(code: Long): MoqLiteSubscribeResponseType? = byCode[code] - } -} +// Wire-format payload data classes for moq-lite messages. +// +// The discriminator enums (which message-body to expect at the top of +// a bidi / uni stream) live in `MoqLiteControlCodes.kt`; the ALPN +// negotiation strings live in `MoqLiteAlpn.kt`. This file holds only +// the body shapes the codec encodes / decodes once a discriminator +// has selected one. /** * "I'm interested in broadcasts under this prefix" — the first message