Skip to main content

moqtap_codec/draft21/
message.rs

1//! Draft-21 control message encoding and decoding.
2//!
3//! Key differences from draft-19:
4//! - **FETCH (0x16) is a different message.** The `Fetch Type` field is gone,
5//!   and with it the Standalone Fetch and Joining Fetch structures and the
6//!   Fetch Type registry. Track Namespace and Track Name are inline fields of
7//!   FETCH, and the range travels in the `LOCATION_FILTER` parameter
8//!   (Section 9.11, Figure 15).
9//! - **PUBLISH_STATE_NOTIFY (0x22) is new** and carries no Request ID
10//!   (Section 9.10, Figure 14).
11//! - **`LOCATION_FILTER` (0x21) has a new value shape.** The Filter Type enum
12//!   is gone; the shape comes from how many `vi64` fields the value holds
13//!   (Section 9.20.10).
14//! - **`FILL_PARAMETERS` (0x23) is new**: a length-prefixed parameter carrying
15//!   a nested parameter block in its own scope (Section 9.20.16).
16//! - **`INCLUDE_PROPERTIES` (0x35) is new**: a uint8 restricted to 0 and 1
17//!   (Section 9.20.22).
18//! - **Parameter applicability moved off `PUBLISH_OK`.** Six definitions
19//!   dropped it and several gained `PUBLISH`; only `EXPIRES` still names it.
20//! - `PUBLISH_DONE`'s `SUBSCRIPTION_ENDED` status (0x3) and `REQUEST_ERROR`'s
21//!   `INVALID_JOINING_REQUEST_ID` (0x32) are unassigned here.
22//!
23//! Draft-21 restructures draft-20 without changing any of this: it moves the
24//! text and renumbers it, and every reference above and below is draft-21's.
25
26use crate::auth_token::{AuthorizationToken, AUTH_TOKEN_PARAMETER};
27use crate::error::MAX_FULL_TRACK_NAME_LENGTH;
28pub use crate::error::{
29    CodecError, MAX_GOAWAY_URI_LENGTH, MAX_MESSAGE_LENGTH, MAX_NAMESPACE_TUPLE_SIZE,
30    MAX_REASON_PHRASE_LENGTH,
31};
32use crate::kvp::{KeyValuePair, KvpError, KvpValue, MAX_KVP_VALUE_LEN};
33use crate::types::*;
34use crate::varint::{Moqt18 as Wire, VarInt};
35use bytes::{Buf, BufMut};
36
37// ============================================================
38// Parameter encoding helpers for draft-21
39// ============================================================
40
41/// How a parameter value is encoded on the wire.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43enum ParamEncoding {
44    /// Bare varint.
45    Varint,
46    /// Single byte (uint8).
47    Uint8,
48    /// Two consecutive varints (group, object).
49    Location,
50    /// Length-prefixed bytes.
51    LengthPrefixed,
52    /// A Track Namespace as defined in draft-21 Section 8.7: a varint field
53    /// count followed by that many length-prefixed fields.
54    ///
55    /// Not one of the four value encodings draft-21 Section 9.20 lists. A
56    /// parameter definition is free to name an encoding from elsewhere in the
57    /// document, and TRACK_NAMESPACE_PREFIX does exactly that; the field count
58    /// is the only length the wire carries.
59    TrackNamespaceValue,
60}
61
62fn param_encoding(key: u64) -> Option<ParamEncoding> {
63    match key {
64        // 0x02 = OBJECT_DELIVERY_TIMEOUT (Section 9.20.5)
65        // 0x04 = RENDEZVOUS_TIMEOUT (Section 9.20.7). Not MAX_CACHE_DURATION:
66        //        that is Property Type 0x04 in the separate Properties
67        //        registry (Section 16.8), a different namespace that happens
68        //        to reuse the number.
69        // 0x06 = SUBGROUP_DELIVERY_TIMEOUT (Section 9.20.4)
70        // 0x08 = EXPIRES (Section 9.20.17, draft-19's 10.2.15)
71        // 0x0A = FILL_TIMEOUT (Section 9.20.6)
72        // 0x32 = NEW_GROUP_REQUEST (Section 9.20.20, draft-19's 10.2.18)
73        0x02 | 0x04 | 0x06 | 0x08 | 0x0A | 0x32 => Some(ParamEncoding::Varint),
74        // 0x10 = FORWARD (Section 9.20.19), 0x20 = SUBSCRIBER_PRIORITY,
75        // 0x22 = GROUP_ORDER, and 0x35 = INCLUDE_PROPERTIES, new in draft-20.
76        // Section 9.20.22: "The INCLUDE_PROPERTIES parameter (Parameter Type
77        // 0x35) is a uint8."
78        0x10 | 0x20 | 0x22 | 0x35 => Some(ParamEncoding::Uint8),
79        // 0x09 = LARGEST_OBJECT. Draft-21 Section 9.20.18: "The LARGEST_OBJECT
80        //        parameter (Parameter Type 0x9) is a Location." A Location is
81        //        two consecutive varints (Section 9.20), with no length ahead
82        //        of them. The type is odd, which on a Key-Value-Pair would mean
83        //        length-prefixed; Message Parameters are not Key-Value-Pairs
84        //        and the odd/even rule does not reach them.
85        0x09 => Some(ParamEncoding::Location),
86        // 0x34 = TRACK_NAMESPACE_PREFIX. Section 9.20.21: it "uses the Track
87        //        Namespace encoding described in Section 8.7".
88        0x34 => Some(ParamEncoding::TrackNamespaceValue),
89        // 0x03 = AUTHORIZATION_TOKEN
90        // 0x21 = LOCATION_FILTER, whose value draft-20 rebuilt (Section 9.20.10)
91        // 0x23 = FILL_PARAMETERS, new in draft-20. Section 9.20.16: it "uses
92        //        length-prefixed encoding", and the value is a nested
93        //        parameter block rather than opaque bytes
94        // 0x25 = SUBGROUP_FILTER, 0x26 = OBJECTID_FILTER, 0x27 = PRIORITY_FILTER,
95        // 0x28 = OBJECT_PROPERTY_FILTER, 0x29 = TRACK_PROPERTY_FILTER
96        //        (Range Filters, Section 8.6 — draft-19's Section 5.1.3)
97        0x03 | 0x21 | 0x23 | 0x25 | 0x26 | 0x27 | 0x28 | 0x29 => {
98            Some(ParamEncoding::LengthPrefixed)
99        }
100        _ => None,
101    }
102}
103
104/// Whether `value` is inside the range draft-21 allows for a uint8-valued
105/// parameter.
106///
107/// Three of the four uint8 parameters restrict their range and say the receiver
108/// MUST close the session with PROTOCOL_VIOLATION on anything outside it:
109/// GROUP_ORDER allows only Ascending (0x1) and Descending (0x2) (Section
110/// 9.20.9), FORWARD allows only 0 and 1 (Section 9.20.19), and
111/// INCLUDE_PROPERTIES — new in draft-20 — allows only 0 and 1 (Section
112/// 9.20.22). SUBSCRIBER_PRIORITY (Section 9.20.8) uses the whole 0-255 range,
113/// so it has no entry here.
114///
115/// Range-checking on decode is what makes the values usable: an application
116/// that tests `group_order == 2` for descending would otherwise treat 7 as
117/// neither ascending nor descending and carry on.
118fn uint8_value_in_range(key: u64, value: u8) -> bool {
119    match key {
120        // FORWARD (0x10)
121        0x10 => value <= 1,
122        // GROUP_ORDER (0x22)
123        0x22 => value == 1 || value == 2,
124        // INCLUDE_PROPERTIES (0x35), Section 9.20.22: "The allowed values are
125        // 0 (do not send Properties) or 1 (send Properties), and the default
126        // is 1. If an endpoint receives a value outside this range, it MUST
127        // close the session with PROTOCOL_VIOLATION."
128        0x35 => value <= 1,
129        _ => true,
130    }
131}
132
133/// AUTHORIZATION TOKEN, Parameter Type 0x03.
134const AUTHORIZATION_TOKEN: u64 = 0x03;
135
136/// Whether draft-21 lets a message carry parameter type `key` more than once.
137///
138/// Section 9.20 states the default: "Senders MUST NOT repeat the same Parameter
139/// Type in a message unless the parameter definition explicitly allows multiple
140/// instances of that type to be sent in a single message." Two definitions do.
141///
142/// * `AUTHORIZATION_TOKEN` (0x03), Section 8.9: "An Authorization Token MAY
143///   be repeated within a message as long as the combination of Token Type and
144///   Token Value are unique after resolving any aliases." Draft-20 wrote the
145///   same rule as "The AUTHORIZATION TOKEN parameter MAY be repeated"; the
146///   restructure reworded the subject and changed nothing it requires.
147/// * The five Range Filters (0x25 through 0x29), Section 3.3.2 — draft-19's
148///   5.1.3: "The Track Property filter parameter MAY appear multiple times in a
149///   SUBSCRIBE_TRACKS message or REQUEST_UPDATE for it. All other filter
150///   parameters MAY appear multiple times in a FETCH, SUBSCRIBE,
151///   SUBSCRIBE_TRACKS, or REQUEST_UPDATE (on a subscription, from the subscriber
152///   only) message."
153///
154/// # A zero `Type Delta` is "the same type again", not an error
155///
156/// The two rules interact, and **the draft does not say how**. Section 9.20 also
157/// requires that "Parameters MUST be serialized in ascending order by Type", so
158/// a second instance of a repeatable type produces a `Type Delta` of 0 — well
159/// formed only if the decoder reads a zero delta as a repeat rather than as a
160/// malformation. That reading is the one taken here; the alternative makes
161/// Section 3.3.2's permission unusable, because there is no other encoding for
162/// a second filter of the same type.
163///
164/// What the filters may **not** do is repeat the same (Parameter Type, SetID,
165/// Property Type) triple, and Section 3.3.2 answers that with a REQUEST_ERROR
166/// carrying INVALID_FILTER rather than with a session close. A reply an endpoint
167/// sends is not a frame a decoder refuses, so nothing here enforces it — see
168/// [`crate::range_filter`] for the reader an endpoint uses to decide.
169fn parameter_may_repeat(key: u64) -> bool {
170    matches!(key, AUTHORIZATION_TOKEN | 0x25..=0x29)
171}
172
173/// Add a delta to the previous delta-encoded key.
174///
175/// Draft-21 Section 8.3: "The previous Type value plus the Delta Type MUST NOT
176/// be greater than 2^64 - 1. If a Delta Type is received that would be too
177/// large, the Session MUST be closed with a PROTOCOL_VIOLATION." MoQT varints
178/// span the whole 64-bit range, so a peer can drive the sum past the end: a
179/// debug build panicked on the addition and a release build wrapped the key and
180/// reported the parameter under a type its sender never wrote.
181fn add_delta(prev_key: u64, delta: u64) -> Result<u64, CodecError> {
182    prev_key.checked_add(delta).ok_or(CodecError::KeyDeltaOverflow(prev_key, delta))
183}
184
185/// Hold a namespace-plus-name pair to the Full Track Name cap.
186///
187/// Draft-21 Section 8.7: "The maximum total length of a Full Track Name is
188/// 4,096 bytes. The length of a Full Track Name is computed as the sum of the
189/// Track Namespace Field Length fields and the Track Name Length field... If an
190/// endpoint receives a Track Namespace or a Full Track Name exceeding 4,096
191/// bytes, it MUST close the session with a PROTOCOL_VIOLATION."
192///
193/// The namespace half of that sentence is enforced inside the namespace decoder,
194/// which is the only place that sees a namespace with no name beside it. This is
195/// the other half, and it has to live where the two are decoded together: a
196/// namespace at 4,000 bytes and a name at 500 are each legal alone.
197fn check_full_track_name(namespace: &TrackNamespace, track_name: &[u8]) -> Result<(), CodecError> {
198    let total = namespace.field_bytes_len().saturating_add(track_name.len());
199    if total > MAX_FULL_TRACK_NAME_LENGTH {
200        return Err(CodecError::TrackNameTooLong);
201    }
202    Ok(())
203}
204
205/// Hold every AUTHORIZATION TOKEN parameter to the Token structure it names.
206///
207/// Section 8.9: "If the Token structure cannot be decoded, the receiver
208/// MUST close the Session with KEY_VALUE_FORMATTING_ERROR." That is the answer
209/// Section 8.3 gives for any Type whose value does not match the
210/// serialization that Type defines; the Token is the one structure this draft
211/// spells out, and the only parameter value in it that is more than opaque
212/// bytes.
213///
214/// Both namespaces carry the type on this draft, and both reach here.
215///
216/// A type this draft cannot name is left alone. The rule is conditional on the
217/// receiver understanding the Type, and an extension's parameter carries bytes
218/// no rule here describes.
219fn check_authorization_tokens(parameters: &[KeyValuePair]) -> Result<(), CodecError> {
220    for parameter in parameters {
221        let key = parameter.key.into_inner();
222        if key != AUTH_TOKEN_PARAMETER {
223            continue;
224        }
225        match &parameter.value {
226            KvpValue::Bytes(value) => {
227                AuthorizationToken::decode_moqt::<Wire>(key, value)?;
228            }
229            // Unreachable from the decoder, which picks the shape from the
230            // type and finds this one length-prefixed. A caller that built the
231            // pair in memory can still get here, and it is the same rule: the
232            // value is not the serialization the type defines.
233            KvpValue::Varint(_) => {
234                return Err(CodecError::KeyValueFormatting {
235                    key,
236                    detail: "its value is a bare varint where the type defines a Token structure",
237                });
238            }
239        }
240    }
241    Ok(())
242}
243
244/// The LOCATION_FILTER parameter type, draft-21 Section 9.20.10.
245pub const LOCATION_FILTER: u64 = 0x21;
246
247/// The FILL_PARAMETERS parameter type, draft-21 Section 9.20.16. New in
248/// draft-20.
249pub const FILL_PARAMETERS: u64 = 0x23;
250
251/// The most `vi64` fields a `LOCATION_FILTER` value can hold: `StartGroup`,
252/// `StartObject`, `EndGroupDelta`, `EndObject` (draft-21 Section 9.20.10).
253const LOCATION_FILTER_MAX_FIELDS: usize = 4;
254
255/// The parameter types draft-21 Section 9.20.16, Table 6 permits inside a
256/// `FILL_PARAMETERS` value, in ascending order.
257///
258/// `TRACK_PROPERTY_FILTER` (0x29) is deliberately absent: a fill applies to one
259/// already-selected track, so a filter that selects tracks has nothing to do
260/// there. The draft does not say that in as many words, but it does say what
261/// happens to one that arrives anyway — "An endpoint that receives a parameter
262/// inside FILL_PARAMETERS that is not listed above MUST close the session with
263/// PROTOCOL_VIOLATION" — which is what makes the omission load-bearing rather
264/// than editorial. A relay forwarding a downstream `FILL_PARAMETERS` upstream
265/// has to strip it rather than pass it on.
266const FILL_PARAMETERS_ALLOWED: &[u64] = &[0x0A, 0x20, 0x21, 0x22, 0x25, 0x26, 0x27, 0x28];
267
268/// Decode the `vi64` fields of a draft-21 `LOCATION_FILTER` value.
269///
270/// Returns between zero and four values, in the wire order `StartGroup`,
271/// `StartObject`, `EndGroupDelta`, `EndObject`. Which of the five shapes the
272/// filter is comes from how many came back; draft-21 Section 9.20.10 gives the
273/// table.
274///
275/// # The field count comes from parsing, never from the byte length
276///
277/// The draft says "Length (in bytes) determines how many optional vi64 fields
278/// are present", and that is not implementable as written. MoQT varints are one
279/// to nine bytes wide and Section 8.1 permits non-minimal encodings, so a
280/// `Length` of 2 is equally consistent with two one-byte fields and one two-byte
281/// field. **This codec decodes `vi64` values until exactly `Length` bytes have
282/// been consumed and then switches on the count.** That is the only rule that
283/// round-trips, and it is a decision this codec makes: the draft states the
284/// byte-length reading and no other.
285///
286/// Two corollaries follow that the draft also does not state, and both are
287/// chosen here:
288///
289/// * a field that would run past `Length` makes the parameter malformed, rather
290///   than being truncated or read from the bytes after the parameter;
291/// * more than four decoded values makes it malformed, because Section 9.20.10
292///   defines shapes for zero through four and nothing beyond.
293///
294/// A decoder that switched on the byte length would notice neither.
295pub fn decode_location_filter(value: &[u8]) -> Result<Vec<u64>, CodecError> {
296    let mut fields: Vec<u64> = Vec::with_capacity(LOCATION_FILTER_MAX_FIELDS);
297    let mut cursor = value;
298    while cursor.has_remaining() {
299        if fields.len() == LOCATION_FILTER_MAX_FIELDS {
300            return Err(CodecError::SubscriptionFilterMalformed {
301                detail: "it holds more than the four vi64 fields Section 3.3.1 defines",
302            });
303        }
304        // The slice is already bounded by the parameter's Length, so a varint
305        // that wants more bytes than remain is one that would have run past it.
306        let field = VarInt::decode_moqt::<Wire>(&mut cursor).map_err(|_| {
307            CodecError::SubscriptionFilterMalformed {
308                detail: "a field runs past the end of the parameter's Length",
309            }
310        })?;
311        fields.push(field.into_inner());
312    }
313    Ok(fields)
314}
315
316/// Hold a decoded `LOCATION_FILTER` to the one arithmetic rule draft-21 states
317/// about it.
318///
319/// Section 3.3.1: "EndGroupDelta is delta encoded from StartGroup, but both the
320/// start and end groups are absolute, not relative to Largest Object. If
321/// StartGroup + EndGroupDelta exceeds 2^64 - 1, the endpoint MUST close the
322/// session with a PROTOCOL_VIOLATION." The sum only exists once three or four
323/// fields are present, so the shorter shapes have nothing to check.
324///
325/// There is deliberately no check that the end is at or after the start.
326/// `EndGroupDelta` is unsigned and added to `StartGroup`, so the end group can
327/// never precede the start group; and within one group draft-21 states no rule
328/// about `EndObject` being below `StartObject`. Section 3.3.1 says the opposite
329/// for subscriptions — "A Location Filter on a subscription is always valid,
330/// even if it specifies a range entirely before Largest Object" — so refusing
331/// one would close sessions over a sentence that is not there.
332fn check_location_filter_fields(fields: &[u64]) -> Result<(), CodecError> {
333    if fields.len() < 3 {
334        return Ok(());
335    }
336    let start_group = fields[0];
337    let delta = fields[2];
338    if start_group.checked_add(delta).is_none() {
339        return Err(CodecError::FilterEndGroupOverflow { start_group, delta });
340    }
341    Ok(())
342}
343
344/// Decode the nested parameter block a `FILL_PARAMETERS` value carries.
345///
346/// # The value begins with a `Number of Parameters` count
347///
348/// Section 9.20.16 says the value is "a sequence of Parameters that apply to
349/// the fill fetch stream" and is "encoded as if they were Parameters for a
350/// separate message", and stops there. **This codec reads that as the
351/// full block, count included.** The draft does not state it either way, and the
352/// wrong choice desynchronises the whole outer parameter list rather than
353/// producing a recognisable error, so it is worth naming: Section 9.20 defines a
354/// parameter block as count-bounded — "Because unknown parameters cannot be
355/// skipped, the block is bounded by a parameter count rather than a length" —
356/// and the phrase *Parameters for a message* denotes that block everywhere else
357/// in Section 9. The outer length prefix is the generic length-prefixed value
358/// encoding every such parameter gets and says nothing about the value's
359/// internal structure.
360///
361/// So an empty `FILL_PARAMETERS` — the common case, a fill with every setting
362/// inherited from the subscription — is `Length = 1` carrying the single byte
363/// `0x00`, and **not** `Length = 0`.
364///
365/// # The `Type Delta` chain restarts here, and the outer chain is unaffected
366///
367/// Section 9.20.16: "The value of FILL_PARAMETERS is a separate parameter
368/// scope. Parameters inside it are not considered to appear in the enclosing
369/// message for the purposes of Section 9.20, so a Parameter Type MAY appear
370/// both in the message and inside FILL_PARAMETERS." Section 9.20 defines
371/// `Type Delta` as the difference from "the previous Parameter Type in the
372/// message", and a separate scope is not the message — so the inner chain
373/// starts from 0, and the outer parameter after `FILL_PARAMETERS` deltas from
374/// `0x23` rather than from the last inner type. **The draft states neither
375/// half explicitly**; both are decided here.
376///
377/// # What it refuses
378///
379/// A type outside Table 6 is [`CodecError::ParameterOutOfScope`], reported
380/// against `FILL_PARAMETERS` itself because the nested scope is the "message"
381/// the parameter appeared in. A type draft-21 does not define at all is
382/// [`CodecError::UnknownMessageParameter`], checked first so an unknown type is
383/// not reported as a known one in the wrong place.
384pub fn decode_fill_parameters(value: &[u8]) -> Result<Vec<KeyValuePair>, CodecError> {
385    let mut cursor = value;
386    let count = VarInt::decode_moqt::<Wire>(&mut cursor)?.into_inner() as usize;
387    let mut params = crate::types::reserve_bounded(count, &cursor);
388    // A separate scope, so the chain starts from 0 exactly as a message's does.
389    let mut prev_key: u64 = 0;
390
391    for i in 0..count {
392        let delta = VarInt::decode_moqt::<Wire>(&mut cursor)?.into_inner();
393        let abs_key = add_delta(prev_key, delta)?;
394        if i > 0 && delta == 0 && !parameter_may_repeat(abs_key) {
395            return Err(CodecError::DuplicateParameter(abs_key));
396        }
397        prev_key = abs_key;
398
399        let encoding =
400            param_encoding(abs_key).ok_or(CodecError::UnknownMessageParameter(abs_key))?;
401        if !FILL_PARAMETERS_ALLOWED.contains(&abs_key) {
402            return Err(CodecError::ParameterOutOfScope {
403                key: abs_key,
404                message_type: FILL_PARAMETERS,
405            });
406        }
407
408        let value = decode_parameter_value(encoding, abs_key, &mut cursor)?;
409        params.push(KeyValuePair { key: VarInt::from_u64_moqt(abs_key), value });
410    }
411
412    // The block is count-bounded and the parameter is length-bounded, and the
413    // two have to agree. Bytes left over mean the count was short of what the
414    // sender wrote, which is the same disagreement a control message's Length
415    // field can have with its body.
416    if cursor.has_remaining() {
417        return Err(CodecError::SubscriptionFilterMalformed {
418            detail: "its parameter count leaves bytes unread inside FILL_PARAMETERS",
419        });
420    }
421    check_location_filters(&params)?;
422    Ok(params)
423}
424
425/// Hold every LOCATION_FILTER and FILL_PARAMETERS parameter to the structure
426/// its own type names.
427///
428/// Applied on both sides. A value the decoder refuses is one the peer must
429/// close the session over, so writing it is a way to end a session rather than
430/// a way to ask for anything.
431///
432/// The two are checked together because `FILL_PARAMETERS` can carry a
433/// `LOCATION_FILTER` of its own, and a filter that is malformed one level down
434/// is exactly as malformed. [`decode_fill_parameters`] recurses back into this
435/// function for that reason; the nesting bottoms out because Table 6 does not
436/// list `FILL_PARAMETERS` inside itself.
437///
438/// The values are otherwise decoded and discarded. What is kept is the refusal
439/// — each stays on its parameter as the bytes that arrived, so a caller reads
440/// one through [`decode_location_filter`] or [`decode_fill_parameters`] when it
441/// wants the structure rather than the frame.
442fn check_location_filters(parameters: &[KeyValuePair]) -> Result<(), CodecError> {
443    for parameter in parameters {
444        let key = parameter.key.into_inner();
445        if key != LOCATION_FILTER && key != FILL_PARAMETERS {
446            continue;
447        }
448        match &parameter.value {
449            KvpValue::Bytes(value) if key == LOCATION_FILTER => {
450                check_location_filter_fields(&decode_location_filter(value)?)?;
451            }
452            KvpValue::Bytes(value) => {
453                decode_fill_parameters(value)?;
454            }
455            // Unreachable from the decoder, which picks the shape from the type
456            // and finds both of these length-prefixed. A caller that built the
457            // pair in memory can still get here, and it is the same rule.
458            KvpValue::Varint(_) => {
459                return Err(CodecError::SubscriptionFilterMalformed {
460                    detail: "its value is a bare varint where the type defines a structure",
461                });
462            }
463        }
464    }
465    Ok(())
466}
467
468/// Read one parameter's value in the shape its type names.
469///
470/// Shared by the message's own parameter block and by the nested block inside
471/// `FILL_PARAMETERS`, so a type cannot be read one way in a message and another
472/// way in a fill.
473fn decode_parameter_value(
474    encoding: ParamEncoding,
475    abs_key: u64,
476    buf: &mut impl Buf,
477) -> Result<KvpValue, CodecError> {
478    Ok(match encoding {
479        ParamEncoding::Varint => KvpValue::Varint(VarInt::decode_moqt::<Wire>(buf)?),
480        ParamEncoding::Uint8 => {
481            if buf.remaining() < 1 {
482                return Err(CodecError::UnexpectedEnd);
483            }
484            let byte = buf.get_u8();
485            if !uint8_value_in_range(abs_key, byte) {
486                return Err(CodecError::ParameterValueOutOfRange {
487                    key: abs_key,
488                    value: byte as u64,
489                });
490            }
491            KvpValue::Varint(VarInt::from_u64_moqt(byte as u64))
492        }
493        ParamEncoding::Location => {
494            let group = VarInt::decode_moqt::<Wire>(buf)?;
495            let object = VarInt::decode_moqt::<Wire>(buf)?;
496            let mut encoded = Vec::new();
497            group.encode_moqt::<Wire>(&mut encoded);
498            object.encode_moqt::<Wire>(&mut encoded);
499            KvpValue::Bytes(encoded)
500        }
501        ParamEncoding::LengthPrefixed => {
502            let len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
503            KvpValue::Bytes(read_bytes(buf, len)?)
504        }
505        ParamEncoding::TrackNamespaceValue => {
506            // A prefix of zero fields is legal: Section 2.4.1 puts a Track
507            // Namespace at "between 0 and 32 Track Namespace Fields", and
508            // an empty prefix matches every namespace.
509            let ns = TrackNamespace::decode_allow_empty_moqt::<Wire>(buf)?;
510            let mut encoded = Vec::new();
511            ns.encode_moqt::<Wire>(&mut encoded);
512            KvpValue::Bytes(encoded)
513        }
514    })
515}
516
517/// Decode a count-prefixed list of parameters with delta-encoded types.
518fn decode_parameters(buf: &mut impl Buf) -> Result<Vec<KeyValuePair>, CodecError> {
519    let count = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
520    let mut params = crate::types::reserve_bounded(count, buf);
521    let mut prev_key: u64 = 0;
522
523    for i in 0..count {
524        let delta = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
525        let abs_key = add_delta(prev_key, delta)?;
526        // Types ascend, so a repeat is always a zero delta against the
527        // parameter before it. Draft-21 Section 9.20: "Receivers SHOULD check
528        // that there are no unexpected duplicate parameters and close the
529        // session with PROTOCOL_VIOLATION if found." Downstream code that scans
530        // the list for a key takes whichever copy it meets first, so two
531        // implementations reading one frame can pick opposite values.
532        //
533        // "Unexpected" is what `parameter_may_repeat` reads: a zero delta on a
534        // type whose own definition permits repeats is the second instance,
535        // which is the only encoding such an instance has.
536        if i > 0 && delta == 0 && !parameter_may_repeat(abs_key) {
537            return Err(CodecError::DuplicateParameter(abs_key));
538        }
539        prev_key = abs_key;
540
541        // Section 9.20: "All Message Parameters MUST be defined in the
542        // negotiated version of MOQT or negotiated via Setup Options. An
543        // endpoint that receives an unknown Message Parameter MUST close the
544        // session with PROTOCOL_VIOLATION. Because the receiver has to
545        // understand every Message Parameter, there is no need for a mechanism
546        // to skip unknown parameters." Because unknown parameters
547        // cannot be skipped, the block is bounded by a parameter count rather
548        // than a length.
549        //
550        // The table this consults is the registry's, so a type it cannot name
551        // is one this draft does not define. Reporting it as an ordinary
552        // malformation would leave the rule enforced against the frame and
553        // invisible to the session.
554        let encoding =
555            param_encoding(abs_key).ok_or(CodecError::UnknownMessageParameter(abs_key))?;
556
557        let value = decode_parameter_value(encoding, abs_key, buf)?;
558
559        params.push(KeyValuePair { key: VarInt::from_u64_moqt(abs_key), value });
560    }
561    check_authorization_tokens(&params)?;
562    check_location_filters(&params)?;
563    Ok(params)
564}
565
566/// Whether `bytes` is exactly the wire form of a Location — two consecutive
567/// varints and nothing after them.
568///
569/// `decode_parameters` builds this value by reading two varints and
570/// re-serialising them, so every value it produces satisfies this. A value
571/// built in memory need not, and the encode arm writes these bytes verbatim
572/// because a Location carries no length of its own. Without this check a
573/// caller could hand over one varint, or three, and the codec would put a
574/// frame on the wire that its own decoder answers with an error.
575fn is_location_value(bytes: &[u8]) -> bool {
576    let mut buf = bytes;
577    VarInt::decode_moqt::<Wire>(&mut buf).is_ok()
578        && VarInt::decode_moqt::<Wire>(&mut buf).is_ok()
579        && !buf.has_remaining()
580}
581
582/// Whether `bytes` is exactly the wire form of a Track Namespace, with
583/// nothing after it. The same reasoning as [`is_location_value`]: the value
584/// goes out verbatim, so it has to be something this draft can read back.
585fn is_track_namespace_value(bytes: &[u8]) -> bool {
586    let mut buf = bytes;
587    TrackNamespace::decode_allow_empty_moqt::<Wire>(&mut buf).is_ok() && !buf.has_remaining()
588}
589
590/// Encode a count-prefixed list of parameters with delta-encoded types.
591///
592/// Errors with [`CodecError::InvalidField`] on a uint8-valued parameter whose
593/// value [`decode_parameters`] would refuse, so the two directions accept the
594/// same set of frames.
595///
596/// The check is not a mirror added for tidiness. A uint8 parameter's value is
597/// written as one octet, and a value that does not fit one is otherwise
598/// truncated to its low byte: GROUP_ORDER 258 becomes the byte 0x02, which is
599/// Descending — a well-formed frame carrying a value the caller never asked
600/// for, and one no receiver could tell from a genuine Descending. Refusing is
601/// the only outcome that does not silently rewrite the message.
602///
603/// The two structure rules are here for a plainer reason. A value under a type
604/// that defines a structure and is not that structure — a Token, a filter — is
605/// one the receiver must close the session over, so writing it is not a way to
606/// send it; the sender's first sign of trouble would be the session going.
607fn encode_parameters(params: &[KeyValuePair], buf: &mut impl BufMut) -> Result<(), CodecError> {
608    check_authorization_tokens(params)?;
609    check_location_filters(params)?;
610    VarInt::from_usize(params.len()).encode_moqt::<Wire>(buf);
611    let mut prev_key: u64 = 0;
612
613    for (i, p) in params.iter().enumerate() {
614        let abs_key = p.key.into_inner();
615        // The delta is a difference, so a descending pair wraps the subtraction
616        // into a nine-byte delta the peer resolves to an unrelated key, and a
617        // repeated type is a frame `decode_parameters` refuses. Both are
618        // refused here so the two directions accept the same set of frames.
619        let delta = abs_key
620            .checked_sub(prev_key)
621            .ok_or(CodecError::ParametersOutOfOrder(prev_key, abs_key))?;
622        if i > 0 && delta == 0 && !parameter_may_repeat(abs_key) {
623            return Err(CodecError::DuplicateParameter(abs_key));
624        }
625        prev_key = abs_key;
626        VarInt::from_u64_moqt(delta).encode_moqt::<Wire>(buf);
627
628        // The same maximum the decoder below applies, and the same one this
629        // draft's Setup Option encoder has always applied: "The maximum length
630        // of a value is 2^16-1 bytes. If an endpoint receives a length larger
631        // than the maximum, it MUST close the session with a PROTOCOL_VIOLATION."
632        // A value past it is one the peer must end the session over, so writing
633        // it is not a way to send it.
634        //
635        // Hoisted above the shape table rather than repeated inside it: a
636        // Location is bytes as well, and one past the maximum is not a Location.
637        if let KvpValue::Bytes(b) = &p.value {
638            if b.len() > MAX_KVP_VALUE_LEN {
639                return Err(KvpError::ValueTooLong(b.len()).into());
640            }
641        }
642
643        let encoding = param_encoding(abs_key);
644        match (&p.value, encoding) {
645            (KvpValue::Varint(v), Some(ParamEncoding::Varint)) => {
646                v.encode_moqt::<Wire>(buf);
647            }
648            (KvpValue::Varint(v), Some(ParamEncoding::Uint8)) => {
649                let raw = v.into_inner();
650                let byte = u8::try_from(raw).map_err(|_| CodecError::InvalidField)?;
651                if !uint8_value_in_range(abs_key, byte) {
652                    return Err(CodecError::ParameterValueOutOfRange {
653                        key: abs_key,
654                        value: byte as u64,
655                    });
656                }
657                buf.put_u8(byte);
658            }
659            // Both values are already stored in their own wire form — two
660            // varints for a Location, a field count and its fields for a Track
661            // Namespace — so they go out as they are. Adding a length here is
662            // the bug these arms exist to avoid.
663            (KvpValue::Bytes(b), Some(ParamEncoding::Location)) => {
664                if !is_location_value(b) {
665                    return Err(CodecError::InvalidField);
666                }
667                buf.put_slice(b);
668            }
669            (KvpValue::Bytes(b), Some(ParamEncoding::TrackNamespaceValue)) => {
670                if !is_track_namespace_value(b) {
671                    return Err(CodecError::InvalidField);
672                }
673                buf.put_slice(b);
674            }
675            (KvpValue::Bytes(b), Some(ParamEncoding::LengthPrefixed)) => {
676                VarInt::from_usize(b.len()).encode_moqt::<Wire>(buf);
677                buf.put_slice(b);
678            }
679            _ => {
680                // Fallback: encode as KVP even/odd
681                match &p.value {
682                    KvpValue::Varint(v) => v.encode_moqt::<Wire>(buf),
683                    KvpValue::Bytes(b) => {
684                        VarInt::from_usize(b.len()).encode_moqt::<Wire>(buf);
685                        buf.put_slice(b);
686                    }
687                }
688            }
689        }
690    }
691    Ok(())
692}
693
694/// Decode delta-encoded KVPs with even/odd convention (for setup options
695/// and track properties). Read until buffer is exhausted.
696fn decode_kvp_delta(buf: &mut impl Buf) -> Result<Vec<KeyValuePair>, CodecError> {
697    let mut pairs = Vec::new();
698    let mut prev_key: u64 = 0;
699
700    while buf.has_remaining() {
701        let delta = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
702        let abs_key = add_delta(prev_key, delta)?;
703        prev_key = abs_key;
704
705        let value = if abs_key.is_multiple_of(2) {
706            let v = VarInt::decode_moqt::<Wire>(buf)?;
707            KvpValue::Varint(v)
708        } else {
709            let len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
710            // Draft-21 Section 8.3: "The maximum length of a value is 2^16-1
711            // bytes. If an endpoint receives a length larger than the maximum,
712            // it MUST close the session with a PROTOCOL_VIOLATION." The
713            // standalone `KeyValuePair::decode` already enforces this; stating
714            // it here too means the two readers of the same wire shape answer
715            // the same way, rather than this one leaning on the caller having
716            // clipped the buffer to a control message first.
717            if len > MAX_KVP_VALUE_LEN {
718                return Err(KvpError::ValueTooLong(len).into());
719            }
720            let data = read_bytes(buf, len)?;
721            KvpValue::Bytes(data)
722        };
723
724        pairs.push(KeyValuePair { key: VarInt::from_u64_moqt(abs_key), value });
725    }
726    Ok(pairs)
727}
728
729/// Encode delta-encoded KVPs with even/odd convention.
730///
731/// Refuses a list that is not in ascending order by type, for the same reason
732/// [`encode_parameters`] does: the delta is a difference, and a descending pair
733/// wraps it into a nine-byte delta the peer resolves to an unrelated key.
734fn encode_kvp_delta(pairs: &[KeyValuePair], buf: &mut impl BufMut) -> Result<(), CodecError> {
735    let mut prev_key: u64 = 0;
736    for p in pairs {
737        let abs_key = p.key.into_inner();
738        let delta = abs_key
739            .checked_sub(prev_key)
740            .ok_or(CodecError::ParametersOutOfOrder(prev_key, abs_key))?;
741        prev_key = abs_key;
742        VarInt::from_u64_moqt(delta).encode_moqt::<Wire>(buf);
743        match &p.value {
744            KvpValue::Varint(v) => v.encode_moqt::<Wire>(buf),
745            KvpValue::Bytes(b) => {
746                if b.len() > MAX_KVP_VALUE_LEN {
747                    return Err(KvpError::ValueTooLong(b.len()).into());
748                }
749                VarInt::from_usize(b.len()).encode_moqt::<Wire>(buf);
750                buf.put_slice(b);
751            }
752        }
753    }
754    Ok(())
755}
756
757/// Immutable Properties, Property Type 0xB.
758///
759/// Section 10.7: Immutable Properties are "a Track or Object Property that
760/// contains a sequence of Key-Value-Pairs (see Figure 2) that are themselves
761/// Track or Object Properties, respectively". The Type is odd, so its value is
762/// length-prefixed bytes, and those bytes are another delta-typed run starting
763/// from 0.
764const IMMUTABLE_PROPERTIES: u64 = 0x0B;
765
766/// Whether `value` is inside the range draft-21 allows for a Track Property
767/// type that restricts one.
768///
769/// Two types do, and each answers anything outside its range with a session
770/// close. DEFAULT_PUBLISHER_GROUP_ORDER (0x22), Section 10.5: "The allowed
771/// values are Ascending (0x1) or Descending (0x2). If an endpoint receives a
772/// value outside this range, it MUST close the session with
773/// PROTOCOL_VIOLATION." DYNAMIC_GROUPS (0x30), Section 10.6: "The allowed
774/// values are 0 or 1... If an endpoint receives a value larger than 1, it MUST
775/// close the session with PROTOCOL_VIOLATION."
776///
777/// Both are Track Properties, so the list they arrive in is the one carried by
778/// a control message rather than the properties on an object.
779///
780/// DEFAULT_PUBLISHER_PRIORITY (0x0E) is not here. Section 10.4 says
781/// "Priorities above 255 are invalid" and stops, where the two above name a
782/// consequence in the next clause. A range stated without one is not a close.
783///
784/// The numbers belong to the Property registry and not the Message Parameter
785/// one. Type 0x22 is GROUP_ORDER as a parameter and
786/// DEFAULT_PUBLISHER_GROUP_ORDER as a property, and the two happen to permit the
787/// same pair of values while meaning different things — one subscriber's
788/// preference against a property of the track. Reading either table for the
789/// other's types would be right by accident here and wrong at the next entry.
790fn track_property_value_in_range(key: u64, value: u64) -> bool {
791    match key {
792        // DEFAULT_PUBLISHER_GROUP_ORDER (0x22)
793        0x22 => value == 1 || value == 2,
794        // DYNAMIC_GROUPS (0x30)
795        0x30 => value <= 1,
796        _ => true,
797    }
798}
799
800/// Refuse a Track Property whose value falls outside the range its type allows,
801/// wherever in the list it is carried.
802///
803/// # Inside Immutable Properties as well as beside them
804///
805/// The list is walked one level down through Immutable Properties, whose
806/// contents Section 10.7 defines as properties themselves. The draft asks for
807/// this in as many words: "When looking for the value of a property, processors
808/// MUST search both the mutable properties and the contents of Immutable
809/// Properties." A check applied only to the outer list is one a peer opts out of
810/// by moving a pair inside the block, and the block is where an Original
811/// Publisher puts what a relay must not rewrite — which is where a track's group
812/// order and dynamic-group support belong.
813///
814/// Bytes under 0xB that do not parse as a Key-Value-Pair run are left alone
815/// rather than refused. Section 10.7 says relays "MAY decode and view the
816/// Properties in the Key-Value-Pairs", which is a permission and not a
817/// requirement, so a block this codec cannot read is carried to the caller
818/// intact instead of ending the session.
819fn check_track_property_values(properties: &[KeyValuePair]) -> Result<(), CodecError> {
820    for property in properties {
821        let key = property.key.into_inner();
822        match &property.value {
823            KvpValue::Varint(value) => {
824                let value = value.into_inner();
825                if !track_property_value_in_range(key, value) {
826                    return Err(CodecError::TrackPropertyValueOutOfRange { key, value });
827                }
828            }
829            KvpValue::Bytes(bytes) if key == IMMUTABLE_PROPERTIES => {
830                let mut inner = &bytes[..];
831                // A block that is not a Key-Value-Pair run is skipped rather
832                // than refused. See the note above: reading inside it is a
833                // permission, so one that cannot be read is carried.
834                //
835                // Skipped means this block and only this block. The rule the
836                // draft states here is about the block whose pairs will not
837                // parse, and says nothing about its neighbours; ending the
838                // whole walk would let a peer keep an out-of-range property
839                // from being looked at by putting an unparseable block in
840                // front of it.
841                if let Ok(nested) = decode_kvp_delta(&mut inner) {
842                    check_track_property_values(&nested)?;
843                }
844            }
845            KvpValue::Bytes(_) => {}
846        }
847    }
848    Ok(())
849}
850
851/// Decode the Track Properties that fill the tail of a control message.
852///
853/// [`decode_kvp_delta`] with the Property registry's value rules applied. The
854/// two are separate because that function also reads Setup Options, which are a
855/// third namespace numbering its entries independently of this one.
856fn decode_track_properties(buf: &mut impl Buf) -> Result<Vec<KeyValuePair>, CodecError> {
857    let properties = decode_kvp_delta(buf)?;
858    check_track_property_values(&properties)?;
859    Ok(properties)
860}
861
862/// Encode a control message's Track Properties.
863///
864/// Held to the same value ranges as the decoder. A value this codec refuses to
865/// read is one it must not write: the peer that receives it is required to close
866/// the session, so the sender's first sign of trouble would be the session
867/// going.
868fn encode_track_properties(
869    properties: &[KeyValuePair],
870    buf: &mut impl BufMut,
871) -> Result<(), CodecError> {
872    check_track_property_values(properties)?;
873    encode_kvp_delta(properties, buf)
874}
875
876/// The Setup Option types this draft defines.
877///
878/// Section 9.1 assigns PATH, AUTHORIZATION TOKEN, MAX_AUTH_TOKEN_CACHE_SIZE, AUTHORITY,
879/// MAX_FILTER_RANGES, MOQT_IMPLEMENTATION and MAX_REQUEST_UPDATES.
880///
881/// The list exists for one rule and one direction. Section 9.1: "Receivers
882/// MUST allow duplicates of unknown Setup Options." A receiver may therefore
883/// refuse a repeat only of a type it can name, and an option outside this list
884/// is one an extension defined and this codec has no business closing a session
885/// over. Nothing else reads it - unknown options are still decoded and carried,
886/// as "Receivers MUST ignore unrecognized Setup Options" requires.
887const KNOWN_SETUP_OPTIONS: &[u64] = &[0x01, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
888
889/// The one Setup Option whose definition allows more than one instance.
890///
891/// Section 9.1.4: "The AUTHORIZATION TOKEN Setup Option (Option Type 0x03)
892/// is functionally equivalent to the AUTHORIZATION TOKEN message parameter...
893/// The endpoint can specify one or more tokens in SETUP that the peer can use to
894/// authorize MOQT session establishment." That is the "unless the option
895/// definition explicitly allows multiple instances" carve-out, and it is the
896/// only one on this draft.
897const REPEATABLE_SETUP_OPTION: u64 = 0x03;
898
899/// Decode the Setup Options of a SETUP message.
900///
901/// Section 9.1: "Senders MUST NOT repeat the same Option Type in a message
902/// unless the option definition explicitly allows multiple instances. Receivers
903/// MUST allow duplicates of unknown Setup Options."
904///
905/// The second sentence is why this is not the mirror of
906/// [`encode_setup_options`]: a repeat of a type this draft names is refused, and
907/// a repeat of any other type is carried. Types ascend and are delta-encoded, so
908/// a repeat is always a zero delta against the option before it.
909fn decode_setup_options(buf: &mut impl Buf) -> Result<Vec<KeyValuePair>, CodecError> {
910    let options = decode_kvp_delta(buf)?;
911    for (i, option) in options.iter().enumerate() {
912        let key = option.key.into_inner();
913        if key == REPEATABLE_SETUP_OPTION || !KNOWN_SETUP_OPTIONS.contains(&key) {
914            continue;
915        }
916        if options[..i].iter().any(|earlier| earlier.key == option.key) {
917            return Err(CodecError::DuplicateParameter(key));
918        }
919    }
920    check_authorization_tokens(&options)?;
921    Ok(options)
922}
923
924/// Encode the Setup Options of a SETUP message.
925///
926/// The sender's half of the same sentence, and it is the wider half: "Senders
927/// MUST NOT repeat the same Option Type in a message" names no exception for
928/// types the sender does not recognise, so every repeat is refused here except
929/// the one the draft allows. A caller holding an option this codec has never
930/// heard of still may not send it twice.
931///
932/// The token is in this namespace as well, and is held to its structure here for
933/// the reason [`encode_parameters`] gives.
934fn encode_setup_options(options: &[KeyValuePair], buf: &mut impl BufMut) -> Result<(), CodecError> {
935    check_authorization_tokens(options)?;
936    for (i, option) in options.iter().enumerate() {
937        if option.key.into_inner() == REPEATABLE_SETUP_OPTION {
938            continue;
939        }
940        if options[..i].iter().any(|earlier| earlier.key == option.key) {
941            return Err(CodecError::DuplicateParameter(option.key.into_inner()));
942        }
943    }
944    encode_kvp_delta(options, buf)
945}
946
947// ============================================================
948// Message Types
949// ============================================================
950
951#[derive(Debug, Clone, Copy, PartialEq, Eq)]
952#[repr(u64)]
953pub enum MessageType {
954    RequestUpdate = 0x02,
955    Subscribe = 0x03,
956    SubscribeOk = 0x04,
957    RequestError = 0x05,
958    PublishNamespace = 0x06,
959    /// REQUEST_OK (0x07). Draft-21 Section 9.3 uses PUBLISH_OK as a shorthand
960    /// for a REQUEST_OK sent in response to a PUBLISH.
961    RequestOk = 0x07,
962    Namespace = 0x08,
963    PublishDone = 0x0B,
964    TrackStatus = 0x0D,
965    NamespaceDone = 0x0E,
966    PublishSkipped = 0x0F,
967    GoAway = 0x10,
968    Fetch = 0x16,
969    FetchOk = 0x18,
970    Publish = 0x1D,
971    /// PUBLISH_STATE_NOTIFY (0x22), new in draft-20 (Section 9.10).
972    PublishStateNotify = 0x22,
973    /// SUBSCRIBE_NAMESPACE (renumbered to 0x50 in draft-18).
974    SubscribeNamespace = 0x50,
975    /// SUBSCRIBE_TRACKS (new message in draft-18).
976    SubscribeTracks = 0x51,
977    Setup = 0x2F00,
978}
979
980impl MessageType {
981    pub fn from_id(id: u64) -> Option<Self> {
982        match id {
983            0x02 => Some(MessageType::RequestUpdate),
984            0x03 => Some(MessageType::Subscribe),
985            0x04 => Some(MessageType::SubscribeOk),
986            0x05 => Some(MessageType::RequestError),
987            0x06 => Some(MessageType::PublishNamespace),
988            0x07 => Some(MessageType::RequestOk),
989            0x08 => Some(MessageType::Namespace),
990            0x0B => Some(MessageType::PublishDone),
991            0x0D => Some(MessageType::TrackStatus),
992            0x0E => Some(MessageType::NamespaceDone),
993            0x0F => Some(MessageType::PublishSkipped),
994            0x10 => Some(MessageType::GoAway),
995            0x16 => Some(MessageType::Fetch),
996            0x18 => Some(MessageType::FetchOk),
997            0x1D => Some(MessageType::Publish),
998            0x22 => Some(MessageType::PublishStateNotify),
999            0x50 => Some(MessageType::SubscribeNamespace),
1000            0x51 => Some(MessageType::SubscribeTracks),
1001            0x2F00 => Some(MessageType::Setup),
1002            _ => None,
1003        }
1004    }
1005
1006    pub fn id(&self) -> u64 {
1007        *self as u64
1008    }
1009
1010    /// This type's name in the shared vector corpus: the `message_type` its
1011    /// draft's `codec/messages/*.json` files carry, in `snake_case`.
1012    pub fn name(&self) -> &'static str {
1013        match self {
1014            MessageType::RequestUpdate => "request_update",
1015            MessageType::Subscribe => "subscribe",
1016            MessageType::SubscribeOk => "subscribe_ok",
1017            MessageType::RequestError => "request_error",
1018            MessageType::PublishNamespace => "publish_namespace",
1019            MessageType::RequestOk => "request_ok",
1020            MessageType::Namespace => "namespace",
1021            MessageType::PublishDone => "publish_done",
1022            MessageType::TrackStatus => "track_status",
1023            MessageType::NamespaceDone => "namespace_done",
1024            MessageType::PublishSkipped => "publish_skipped",
1025            MessageType::GoAway => "goaway",
1026            MessageType::Fetch => "fetch",
1027            MessageType::FetchOk => "fetch_ok",
1028            MessageType::Publish => "publish",
1029            MessageType::PublishStateNotify => "publish_state_notify",
1030            MessageType::SubscribeNamespace => "subscribe_namespace",
1031            MessageType::SubscribeTracks => "subscribe_tracks",
1032            MessageType::Setup => "setup",
1033        }
1034    }
1035}
1036
1037// ============================================================
1038// Session Lifecycle Messages
1039// ============================================================
1040
1041/// Unified SETUP (0x2F00).
1042#[derive(Debug, Clone, PartialEq, Eq)]
1043pub struct Setup {
1044    pub options: Vec<KeyValuePair>,
1045}
1046
1047/// GOAWAY (0x10). Draft-21's GOAWAY has no Request ID field, so the
1048/// control-stream and request-stream forms are identical on the wire.
1049/// Draft-18 is the one draft that carries the field, and only when the
1050/// message is sent on the control stream.
1051#[derive(Debug, Clone, PartialEq, Eq)]
1052pub struct GoAway {
1053    pub new_session_uri: Vec<u8>,
1054    pub timeout: VarInt,
1055}
1056
1057// ============================================================
1058// Consolidated Response Messages
1059// ============================================================
1060
1061/// REQUEST_OK (0x07). Used as a generic OK response and as the alias for
1062/// PUBLISH_OK / REQUEST_UPDATE_OK / TRACK_STATUS_OK / SUBSCRIBE_NAMESPACE_OK
1063/// / PUBLISH_NAMESPACE_OK.
1064///
1065/// `track_properties` is only populated for TRACK_STATUS_OK; for every
1066/// other shape it MUST be empty (length implicit from the message length).
1067#[derive(Debug, Clone, PartialEq, Eq)]
1068pub struct RequestOk {
1069    pub parameters: Vec<KeyValuePair>,
1070    pub track_properties: Vec<KeyValuePair>,
1071}
1072
1073/// Optional Redirect structure carried in REQUEST_ERROR with code 0x34.
1074#[derive(Debug, Clone, PartialEq, Eq)]
1075pub struct Redirect {
1076    pub connect_uri: Vec<u8>,
1077    pub track_namespace: TrackNamespace,
1078    pub track_name: Vec<u8>,
1079}
1080
1081/// REQUEST_ERROR (0x05). Adds an optional Redirect structure when
1082/// `error_code` is REDIRECT (0x34).
1083#[derive(Debug, Clone, PartialEq, Eq)]
1084pub struct RequestError {
1085    pub error_code: VarInt,
1086    pub retry_interval: VarInt,
1087    pub reason_phrase: Vec<u8>,
1088    pub redirect: Option<Redirect>,
1089}
1090
1091/// REQUEST_ERROR error codes with dedicated meaning.
1092///
1093/// Drafts 16 through 18 define DUPLICATE_SUBSCRIPTION (0x19) among these
1094/// codes; drafts 19 and 20 do not, because Section 3.1 lets an endpoint hold
1095/// multiple concurrent subscriptions to the same Track, each under its own
1096/// Request ID.
1097pub mod request_error_codes {
1098    /// A Mandatory Track Property the receiver does not understand.
1099    pub const UNSUPPORTED_EXTENSION: u64 = 0x33;
1100    /// Response carries a [`super::Redirect`] structure.
1101    pub const REDIRECT: u64 = 0x34;
1102    /// New in draft-20: SUBSCRIBE_TRACKS filter parameters conflict among too
1103    /// many subscribers to aggregate the subscription upstream.
1104    pub const CONFLICTING_FILTERS: u64 = 0x35;
1105    /// New in draft-20: a Range Filter parameter is invalid or exceeds
1106    /// MAX_FILTER_RANGES.
1107    pub const INVALID_FILTER: u64 = 0x36;
1108}
1109
1110// ============================================================
1111// Subscribe Messages
1112// ============================================================
1113
1114#[derive(Debug, Clone, PartialEq, Eq)]
1115pub struct Subscribe {
1116    pub request_id: VarInt,
1117    pub track_namespace: TrackNamespace,
1118    pub track_name: Vec<u8>,
1119    pub parameters: Vec<KeyValuePair>,
1120}
1121
1122/// SUBSCRIBE_OK (0x04).
1123#[derive(Debug, Clone, PartialEq, Eq)]
1124pub struct SubscribeOk {
1125    pub track_alias: VarInt,
1126    pub parameters: Vec<KeyValuePair>,
1127    pub track_properties: Vec<KeyValuePair>,
1128}
1129
1130#[derive(Debug, Clone, PartialEq, Eq)]
1131pub struct RequestUpdate {
1132    pub request_id: VarInt,
1133    pub parameters: Vec<KeyValuePair>,
1134}
1135
1136// ============================================================
1137// Publish Messages
1138// ============================================================
1139
1140#[derive(Debug, Clone, PartialEq, Eq)]
1141pub struct Publish {
1142    pub request_id: VarInt,
1143    pub track_namespace: TrackNamespace,
1144    pub track_name: Vec<u8>,
1145    pub track_alias: VarInt,
1146    pub parameters: Vec<KeyValuePair>,
1147    pub track_properties: Vec<KeyValuePair>,
1148}
1149
1150/// PUBLISH_DONE (0x0B). The wire layout is unchanged from draft-19; what
1151/// draft-20 changed is the `Stream Count` sentinel, what the count includes,
1152/// and the removal of status code 0x3.
1153#[derive(Debug, Clone, PartialEq, Eq)]
1154pub struct PublishDone {
1155    /// The reason the publisher is ending the subscription.
1156    ///
1157    /// Not validated against the registry, on decode or on encode, and that is
1158    /// the draft's instruction rather than an omission. Draft-21 Section 13:
1159    /// "Receipt of an unknown error code in any error context (Session
1160    /// Termination, REQUEST_ERROR, PUBLISH_DONE, or Data Stream Reset) MUST be
1161    /// treated as equivalent to INTERNAL_ERROR for that context. An endpoint
1162    /// MUST NOT close the session because it received an unknown error code in
1163    /// a REQUEST_ERROR or PUBLISH_DONE." Refusing the frame would take that
1164    /// choice away from the caller, so an unassigned code is carried up and
1165    /// [`super::error_codes::PublishDoneStatusCode::from_u64`] answers `None`
1166    /// for it.
1167    ///
1168    /// That reaches 0x3 in particular. Draft-19 assigned it to
1169    /// `SUBSCRIPTION_ENDED`; draft-20 removed the row and the behaviour behind
1170    /// it together (Section 3.3.1: "A publisher does not end a subscription
1171    /// solely because the Largest Object advances past the end of the current
1172    /// Location Filter"). A draft-21 receiver reads a 0x3 as INTERNAL_ERROR.
1173    pub status_code: VarInt,
1174    /// The number of streams the publisher opened for this subscription.
1175    ///
1176    /// Draft-21 Section 9.9 widens what is counted: the total now includes
1177    /// "streams that contained no Objects (e.g., an empty Subgroup) and
1178    /// including any fill fetch streams (see Section 3.4)". Draft-19 counted
1179    /// no fill streams because it had none.
1180    ///
1181    /// See [`publish_done_codes::STREAM_COUNT_UNKNOWN`] for the sentinel.
1182    pub stream_count: VarInt,
1183    pub reason_phrase: Vec<u8>,
1184}
1185
1186/// Numeric values for the [`PublishDone`] fields.
1187pub mod publish_done_codes {
1188    /// Draft-18 onwards: TOO_FAR_BEHIND is 0x05 (was 0x06 in draft-17).
1189    pub const TOO_FAR_BEHIND: u64 = 0x05;
1190    /// Draft-18 onwards: EXPIRED is 0x06 (was 0x05 in draft-17).
1191    pub const EXPIRED: u64 = 0x06;
1192
1193    /// The value [`super::PublishDone::stream_count`] carries when the
1194    /// publisher cannot state an exact count.
1195    ///
1196    /// Draft-21 Section 9.9: "If the publisher is unable to set Stream Count
1197    /// to the exact number of streams opened for the subscription, it MUST set
1198    /// Stream Count to 2^64 - 1." Draft-19 said `2^62 - 1`, which is where the
1199    /// QUIC varint tops out; MoQT's own varint (Section 8.1) is a
1200    /// leading-ones-length prefix reaching a full 64 bits in nine bytes, so
1201    /// this value is encodable at all — as `ff` followed by eight `ff` bytes.
1202    ///
1203    /// **In draft-21 the sentinel is indistinguishable from a well-formed
1204    /// exact count.** Draft-19's `2^62 - 1` leaves headroom above the marker;
1205    /// there is none above this one, so a publisher that really opened
1206    /// `2^64 - 1` streams cannot say so and a receiver cannot tell the two
1207    /// apart. The draft does not remark on it. Not a practical problem, and
1208    /// worth knowing before writing a comparison against this constant.
1209    pub const STREAM_COUNT_UNKNOWN: u64 = u64::MAX;
1210}
1211
1212// ============================================================
1213// Publish Namespace Messages
1214// ============================================================
1215
1216#[derive(Debug, Clone, PartialEq, Eq)]
1217pub struct PublishNamespace {
1218    pub request_id: VarInt,
1219    pub track_namespace: TrackNamespace,
1220    pub parameters: Vec<KeyValuePair>,
1221}
1222
1223// ============================================================
1224// Namespace Messages
1225// ============================================================
1226
1227#[derive(Debug, Clone, PartialEq, Eq)]
1228pub struct Namespace {
1229    pub namespace_suffix: TrackNamespace,
1230}
1231
1232#[derive(Debug, Clone, PartialEq, Eq)]
1233pub struct NamespaceDone {
1234    pub namespace_suffix: TrackNamespace,
1235}
1236
1237// ============================================================
1238// Subscribe Namespace / Tracks Messages
1239// ============================================================
1240
1241/// SUBSCRIBE_NAMESPACE (0x50). Subscribes to NAMESPACE / NAMESPACE_DONE
1242/// advertisements for namespaces matching `namespace_prefix`. Only drafts 16
1243/// and 17 carry a `subscribe_options` varint here; on draft-21 a namespace
1244/// subscription only produces NAMESPACE / NAMESPACE_DONE.
1245#[derive(Debug, Clone, PartialEq, Eq)]
1246pub struct SubscribeNamespace {
1247    pub request_id: VarInt,
1248    pub namespace_prefix: TrackNamespace,
1249    pub parameters: Vec<KeyValuePair>,
1250}
1251
1252/// SUBSCRIBE_TRACKS (0x51, new in draft-18). Subscribes to PUBLISH messages
1253/// for tracks whose namespace matches `namespace_prefix`. Carries the FORWARD
1254/// parameter (Section 9.20.19), which on drafts 15 through 17 may appear on
1255/// SUBSCRIBE_NAMESPACE instead.
1256#[derive(Debug, Clone, PartialEq, Eq)]
1257pub struct SubscribeTracks {
1258    pub request_id: VarInt,
1259    pub namespace_prefix: TrackNamespace,
1260    pub parameters: Vec<KeyValuePair>,
1261}
1262
1263// ============================================================
1264// Track Status Messages
1265// ============================================================
1266
1267#[derive(Debug, Clone, PartialEq, Eq)]
1268pub struct TrackStatus {
1269    pub request_id: VarInt,
1270    pub track_namespace: TrackNamespace,
1271    pub track_name: Vec<u8>,
1272    pub parameters: Vec<KeyValuePair>,
1273}
1274
1275// ============================================================
1276// Fetch Messages
1277// ============================================================
1278
1279/// FETCH (0x16), rebuilt in draft-20 (Section 9.11, Figure 15).
1280///
1281/// ```text
1282/// FETCH Message {
1283///   Type (vi64) = 0x16,
1284///   Length (16),
1285///   Request ID (vi64),
1286///   Track Namespace (..),
1287///   Track Name Length (vi64),
1288///   Track Name (..),
1289///   Number of Parameters (vi64),
1290///   Parameters (..) ...
1291/// }
1292/// ```
1293///
1294/// # What went, and why draft-19's shape cannot be ported
1295///
1296/// Draft-19's FETCH opened with a `Fetch Type` that chose between a Standalone
1297/// Fetch — a namespace, a name and an inline `Start Location` / `End Location`
1298/// pair — and a Joining Fetch of two varints. Draft-20 deleted the field, both
1299/// structures, the Fetch Type registry and the whole joining mechanism, and
1300/// promoted the namespace and the name to fields of FETCH itself, in the
1301/// positions they hold inside draft-19's Standalone Fetch. What is left is
1302/// byte-identical to [`Subscribe`] apart from the type code.
1303///
1304/// The range now travels in the `LOCATION_FILTER` parameter (Section 3.3.1). A
1305/// FETCH with none covers `{0,0}` through Largest Object, inclusive.
1306///
1307/// # `INVALID_RANGE` and a relative start
1308///
1309/// Section 9.11 keeps draft-19's rule: "If no Objects have been published for
1310/// the track or Start Location is greater than the Largest Object" then "the
1311/// publisher MUST return REQUEST_ERROR with error code INVALID_RANGE". A
1312/// relative start —
1313/// the one-field `LOCATION_FILTER` with `StartGroup = 0`, which resolves to the
1314/// Next Group — is by construction greater than Largest Object, so read
1315/// literally the rule rejects every relative-start FETCH. That is plainly not
1316/// the intent and the text carves out nothing, so **this codec does not apply
1317/// the Start-greater-than-Largest test to a relative start**, and neither
1318/// should a caller. Nothing here can enforce either reading: the test needs
1319/// Largest Object, which is track state rather than anything in this frame, so
1320/// the decision belongs to the endpoint and is recorded here because this is
1321/// where the filter arrives.
1322///
1323/// **The codepoint did not change.** A draft-19 decoder fed one of these reads
1324/// the `Number of Track Namespace Fields` count as a `Fetch Type` and
1325/// mis-parses without complaint; there is no in-band version signal to catch
1326/// it. That is why draft-21 has a FETCH decoder of its own rather than sharing
1327/// draft-19's.
1328#[derive(Debug, Clone, PartialEq, Eq)]
1329pub struct Fetch {
1330    pub request_id: VarInt,
1331    pub track_namespace: TrackNamespace,
1332    pub track_name: Vec<u8>,
1333    pub parameters: Vec<KeyValuePair>,
1334}
1335
1336/// FETCH_OK (0x18). `end_of_track` is uint8.
1337///
1338/// Byte-identical to draft-19; the field that changed meaning is
1339/// [`FetchOk::end_object`].
1340#[derive(Debug, Clone, PartialEq, Eq)]
1341pub struct FetchOk {
1342    pub end_of_track: u8,
1343    pub end_group: VarInt,
1344    /// The Object ID of the **last Object the response covers**, inclusive.
1345    ///
1346    /// This is the silent off-by-one of the revision, and it is absent from
1347    /// draft-20's own change log. Draft-19 Section 10.13 defined the pair as
1348    /// "the end of the range covered by the FETCH response, using the same
1349    /// encoding as the FETCH request End Location (the last Object, plus 1; or
1350    /// 0 to indicate the entire Group)". Draft-21 Section 9.12 drops that
1351    /// parenthesis entirely, and Section 3.3.1 and Section 9.11 both say the
1352    /// Location filter "specifies an inclusive range of Locations".
1353    ///
1354    /// So both draft-19 conventions are gone: there is no plus one, and an
1355    /// Object of 0 now means object 0 rather than the whole group. The bytes
1356    /// are identical between the two drafts and the meaning is not, and nothing
1357    /// on the wire distinguishes them — an encoder ported forward with its
1358    /// arithmetic intact fetches one object too many, and one whose end lands
1359    /// on object 0 fetches a single object where the same bytes cover a whole
1360    /// group on draft-19.
1361    ///
1362    /// **Ambiguous, and the draft leaves it so.** When the request's filter
1363    /// omitted `EndObject`, so the
1364    /// filter meant "all objects in the End Group", Section 9.12 does not say
1365    /// what to report — and with the `0`-means-whole-group encoding gone there
1366    /// is no way left to spell it. The same applies to a relative start. The
1367    /// reading this codec's corpus takes, and the only sane one, is the Object
1368    /// ID of the last Object actually covered; the draft does not say so, and
1369    /// nothing here can enforce it, because resolving it needs the track rather
1370    /// than the frame.
1371    pub end_object: VarInt,
1372    pub parameters: Vec<KeyValuePair>,
1373    pub track_properties: Vec<KeyValuePair>,
1374}
1375
1376// ============================================================
1377// Publish State Notify
1378// ============================================================
1379
1380/// PUBLISH_STATE_NOTIFY (0x22), new in draft-20 (Section 9.10, Figure 14).
1381///
1382/// ```text
1383/// PUBLISH_STATE_NOTIFY Message {
1384///   Type (vi64) = 0x22,
1385///   Length (16),
1386///   Number of Parameters (vi64),
1387///   Parameters (..) ...
1388/// }
1389/// ```
1390///
1391/// **There is no Request ID field.** The message is identified by the
1392/// subscription's bidirectional request stream it arrives on, the way
1393/// SUBSCRIBE_OK, PUBLISH_DONE and FETCH_OK are.
1394///
1395/// The rules a codec cannot check, because they are about the session rather
1396/// than the frame, and which the caller therefore owns (Section 9.10):
1397///
1398/// * it is sent by the **publisher only**, on a **subscription's** stream.
1399///   Receiving one for any other request type, or from the subscriber, MUST
1400///   close the session with a PROTOCOL_VIOLATION;
1401/// * it is **unilateral** — the receiver does not answer with REQUEST_OK or
1402///   REQUEST_ERROR — and it is not counted against the `MAX_REQUEST_UPDATES`
1403///   Setup Option;
1404/// * it carries only the parameters whose values changed, and an absent
1405///   parameter is unchanged;
1406/// * a publisher MUST NOT use it to change a subscriber-controlled parameter
1407///   unless the subscriber asked for the change;
1408/// * the publisher **MUST** include `LARGEST_OBJECT` (0x09) if known, so the
1409///   subscriber can locate the point in the track where the change took
1410///   effect. Nothing here enforces that: a missing required parameter is not
1411///   something this decoder detects, and a message that omits it is still a
1412///   well-formed frame.
1413#[derive(Debug, Clone, PartialEq, Eq)]
1414pub struct PublishStateNotify {
1415    pub parameters: Vec<KeyValuePair>,
1416}
1417
1418// ============================================================
1419// Publish Skipped
1420// ============================================================
1421
1422/// PUBLISH_SKIPPED (0x0F). Drafts 17 and 18 name this codepoint
1423/// PUBLISH_BLOCKED; the wire layout — track namespace suffix then track name —
1424/// is the same on all four drafts that carry the message.
1425#[derive(Debug, Clone, PartialEq, Eq)]
1426pub struct PublishSkipped {
1427    pub namespace_suffix: TrackNamespace,
1428    pub track_name: Vec<u8>,
1429}
1430
1431// ============================================================
1432// Unified Message Enum
1433// ============================================================
1434
1435#[derive(Debug, Clone, PartialEq, Eq)]
1436pub enum ControlMessage {
1437    Setup(Setup),
1438    GoAway(GoAway),
1439    RequestOk(RequestOk),
1440    RequestError(RequestError),
1441    Subscribe(Subscribe),
1442    SubscribeOk(SubscribeOk),
1443    RequestUpdate(RequestUpdate),
1444    Publish(Publish),
1445    PublishStateNotify(PublishStateNotify),
1446    PublishDone(PublishDone),
1447    PublishNamespace(PublishNamespace),
1448    Namespace(Namespace),
1449    NamespaceDone(NamespaceDone),
1450    SubscribeNamespace(SubscribeNamespace),
1451    SubscribeTracks(SubscribeTracks),
1452    TrackStatus(TrackStatus),
1453    Fetch(Fetch),
1454    FetchOk(FetchOk),
1455    PublishSkipped(PublishSkipped),
1456}
1457
1458// Draft-21 has no range check on a control message, and the absence is the
1459// draft's rather than an omission here.
1460//
1461// Draft-19 carried one: its FETCH held a `Start Location` and an `End Location`
1462// inline, and draft-19 Section 10.12.3 said "End Location MUST specify the
1463// same or a larger Location than Start Location for Standalone and Absolute
1464// Joining
1465// Fetches". Draft-20 deleted both fields with the rest of the FETCH rewrite
1466// (Section 9.11), so there is no pair left in any message to compare.
1467//
1468// What replaced them cannot express the malformation either. A `LOCATION_FILTER`
1469// (Section 3.3.1) states its end as an unsigned `EndGroupDelta` added to
1470// `StartGroup`, so the end group can never precede the start group; and within
1471// one group draft-21 states no rule about an `EndObject` below `StartObject`.
1472// Section 3.3.1 says the opposite for subscriptions — "A Location Filter on a
1473// subscription is always valid, even if it specifies a range entirely before
1474// Largest Object" — so refusing one would close sessions over a sentence that
1475// is not there.
1476//
1477// The one backwards-range rule draft-21 does keep is in Section 9.12: "If End
1478// Location is smaller than the Start Location in the corresponding FETCH the
1479// receiver MUST close the session with a PROTOCOL_VIOLATION." It compares a
1480// FETCH_OK against the FETCH it answers, which is session state and not
1481// anything one frame holds, so it belongs to the caller.
1482
1483/// Refuse a message whose discriminator disagrees with the fields beside it.
1484///
1485/// One draft-21 message carries a field that says which of the following fields
1486/// are on the wire: REQUEST_ERROR's Error Code, whose REDIRECT value (0x34) is
1487/// what puts the Redirect structure on the wire. This codec holds the
1488/// alternative in an `Option`, so a value can say one thing in its
1489/// discriminator and another in its body, and the two sides of the codec
1490/// resolve that differently — the encoder writes whatever the body holds, and
1491/// the decoder reads whatever the discriminator announces.
1492///
1493/// A REQUEST_ERROR with code REDIRECT and no Redirect body encodes to a message
1494/// that ends where the decoder expects a Connect URI length, so the peer reads
1495/// the redirect out of whatever follows or runs off the end. The mirror case is
1496/// quieter and no better: a Redirect body under any other error code is written
1497/// out and then skipped by a decoder that was never told to look for it, so the
1498/// sender believes it redirected a peer that never saw a redirect.
1499///
1500/// Draft-19 had a second discriminator here, FETCH's `Fetch Type`, choosing
1501/// between a standalone body and a joining pair. Draft-20 deleted the field and
1502/// both bodies (Section 9.11), so FETCH has nothing left to disagree with
1503/// itself about.
1504///
1505/// Refusing at the encoder keeps the two readings from ever diverging on the
1506/// wire.
1507fn check_discriminators(message: &ControlMessage) -> Result<(), CodecError> {
1508    // One arm, where drafts 17 to 19 have two, so an `if let` rather than a
1509    // one-armed `match`; a second discriminator would make it a `match`.
1510    if let ControlMessage::RequestError(m) = message {
1511        let code_is_redirect = m.error_code.into_inner() == request_error_codes::REDIRECT;
1512        if code_is_redirect != m.redirect.is_some() {
1513            return Err(CodecError::InvalidField);
1514        }
1515    }
1516    Ok(())
1517}
1518
1519/// Whether draft-21 lets Message Parameter `key` appear in `message`.
1520///
1521/// Section 9.20.1: "Each Message Parameter definition indicates the message
1522/// types in which it can appear. If it appears in some other type of message,
1523/// the receiving endpoint MUST close the connection with a PROTOCOL_VIOLATION."
1524/// One arm per entry in the Message Parameters registry (Section 16.7),
1525/// carrying the message types that entry's own definition names.
1526///
1527/// Four things about this draft the arms below fold in:
1528///
1529/// * Six of the names are one wire type. Section 9.3: "This document uses the
1530///   shorthand PUBLISH_OK, REQUEST_UPDATE_OK, TRACK_STATUS_OK,
1531///   SUBSCRIBE_NAMESPACE_OK, SUBSCRIBE_TRACKS_OK and PUBLISH_NAMESPACE_OK to
1532///   refer to a REQUEST_OK sent in response to the corresponding request type".
1533///   Which one a given REQUEST_OK is depends on the request its Request ID
1534///   answers, which is session state and not in the frame, so each of those
1535///   names widens the same arm and a REQUEST_OK is held to their union.
1536/// * The five Range Filters state their scope in Section 3.3.2 — draft-19's
1537///   Section 5.1.3 — rather than in their own subsections: the Track Property
1538///   filter "MAY appear multiple times in a SUBSCRIBE_TRACKS message or
1539///   REQUEST_UPDATE for it", and "all other filter parameters MAY appear
1540///   multiple times in a FETCH, SUBSCRIBE, SUBSCRIBE_TRACKS, or REQUEST_UPDATE
1541///   (on a subscription, from the subscriber only) message". Draft-19 listed
1542///   PUBLISH_OK in that second sentence and draft-20 removed it.
1543/// * **SUBSCRIBE_TRACKS takes the parameters whose own definitions name it,
1544///   plus `LOCATION_FILTER` (0x21) and `FILL_PARAMETERS` (0x23), and the two
1545///   extra are a decision this codec makes.** Section 9.18.1: "Any Parameter
1546///   that can be specified on a Subscription (ie: in SUBSCRIBE) is valid in
1547///   SUBSCRIBE_TRACKS, unless otherwise specified." The definitions are that
1548///   "otherwise": `AUTHORIZATION_TOKEN`, `FORWARD`, `GROUP_ORDER`,
1549///   `INCLUDE_PROPERTIES` and the Range Filters name SUBSCRIBE_TRACKS, and
1550///   `OBJECT_DELIVERY_TIMEOUT` (0x02), `RENDEZVOUS_TIMEOUT` (0x04),
1551///   `SUBGROUP_DELIVERY_TIMEOUT` (0x06), `SUBSCRIBER_PRIORITY` (0x20) and
1552///   `NEW_GROUP_REQUEST` (0x32) leave it out, so those five are refused there
1553///   under Section 9.20.1. The same section then closes: "To join Tracks
1554///   initiated via the resulting PUBLISHes, the subscriber can specify a
1555///   Location Filter and optionally include FILL_PARAMETERS" — two parameters
1556///   whose definitions also leave this message out. On those two alone the
1557///   section and the definitions with Section 9.20.1's rule conflict, from
1558///   draft-20 on. This codec follows the section for them: it is the one
1559///   sentence telling a subscriber what to put in this message, and a scope
1560///   miss ends the session.
1561/// * **`PUBLISH_OK` is gone from six definitions.** `OBJECT_DELIVERY_TIMEOUT`
1562///   (0x02), `SUBGROUP_DELIVERY_TIMEOUT` (0x06), `FORWARD` (0x10),
1563///   `SUBSCRIBER_PRIORITY` (0x20), `LOCATION_FILTER` (0x21) and
1564///   `NEW_GROUP_REQUEST` (0x32) each dropped it, and several gained `PUBLISH`
1565///   instead; the Range Filters dropped it via Section 3.3.2. `EXPIRES` (0x08)
1566///   is the only parameter that still names it. A subscriber that wants to
1567///   change something now sends a REQUEST_UPDATE after the PUBLISH_OK, and
1568///   sending any of the six on a PUBLISH_OK is a Section 9.20.1 violation.
1569///   Because PUBLISH_OK and REQUEST_OK are one wire type, this table cannot
1570///   see the difference: `M::RequestOk` is admitted wherever any of the six OK
1571///   names is, so the practical effect here is that the six lose their
1572///   `M::RequestOk` arm entirely.
1573///
1574/// FETCH_OK has no arm in the table below, and that is the draft's doing rather
1575/// than an omission here: Section 9.12 gives it a Parameters field and no
1576/// parameter definition names it, so every type this draft defines is "some
1577/// other type of message" there.
1578///
1579/// PUBLISH_STATE_NOTIFY is admitted by exactly three parameters, and treating
1580/// that list as closed is **a decision this codec makes**. Section 9.10 says
1581/// only "The semantics of each parameter, including whether it may appear in
1582/// PUBLISH_STATE_NOTIFY, are defined by the parameter", and `LARGEST_OBJECT`
1583/// (0x09), `FORWARD` (0x10) and `LOCATION_FILTER` (0x21) are the three whose
1584/// definitions name it. The draft implies the closure and never states it, so
1585/// anything else there is refused under Section 9.20.1 on that reading.
1586///
1587/// The table decides scope only. A type this draft does not define has no scope
1588/// to be outside of and is answered by [`CodecError::UnknownMessageParameter`],
1589/// which is why the final arm carries rather than refuses.
1590pub fn parameter_in_scope(key: u64, message: MessageType) -> bool {
1591    use MessageType as M;
1592    // SUBSCRIBE_TRACKS appears only in the arms whose definitions name it, and
1593    // in 0x21 and 0x23, which Section 9.18.1 names; see the doc comment above.
1594    match key {
1595        // Section 9.20.5 OBJECT_DELIVERY_TIMEOUT: "It MAY appear in a
1596        // SUBSCRIBE, PUBLISH, or REQUEST_UPDATE message." Draft-19 said
1597        // PUBLISH_OK where this says PUBLISH.
1598        0x02 => matches!(message, M::Subscribe | M::Publish | M::RequestUpdate),
1599        // Section 9.20.3 AUTHORIZATION TOKEN: "It MAY appear in a PUBLISH,
1600        // SUBSCRIBE, REQUEST_UPDATE, SUBSCRIBE_NAMESPACE, SUBSCRIBE_TRACKS,
1601        // PUBLISH_NAMESPACE, TRACK_STATUS or FETCH message." Unchanged from
1602        // draft-19; what draft-20 added is the sentence after it, "This
1603        // Parameter MUST NOT be copied from a SUBSCRIBE_TRACKS to the resulting
1604        // PUBLISH message Parameters", which is a rule about two messages and
1605        // so belongs to the caller rather than to this table.
1606        0x03 => matches!(
1607            message,
1608            M::Publish
1609                | M::Subscribe
1610                | M::RequestUpdate
1611                | M::SubscribeNamespace
1612                | M::SubscribeTracks
1613                | M::PublishNamespace
1614                | M::TrackStatus
1615                | M::Fetch
1616        ),
1617        // Section 9.20.7 RENDEZVOUS TIMEOUT: it "MAY appear in a SUBSCRIBE
1618        // message".
1619        0x04 => matches!(message, M::Subscribe),
1620        // Section 9.20.4 SUBGROUP_DELIVERY_TIMEOUT: "It MAY appear in a
1621        // SUBSCRIBE, PUBLISH, or REQUEST_UPDATE message." Draft-19 said
1622        // PUBLISH_OK where this says PUBLISH.
1623        0x06 => matches!(message, M::Subscribe | M::Publish | M::RequestUpdate),
1624        // Section 9.20.17 EXPIRES: "It MAY appear in SUBSCRIBE_OK, PUBLISH,
1625        // PUBLISH_OK, SUBSCRIBE_NAMESPACE_OK, SUBSCRIBE_TRACKS_OK,
1626        // PUBLISH_NAMESPACE_OK, or REQUEST_UPDATE_OK." Five of those seven are
1627        // a REQUEST_OK. The one parameter that still names PUBLISH_OK.
1628        0x08 => matches!(message, M::SubscribeOk | M::Publish | M::RequestOk),
1629        // Section 9.20.18 LARGEST OBJECT: "It MAY appear in SUBSCRIBE_OK,
1630        // PUBLISH, REQUEST_UPDATE_OK, TRACK_STATUS_OK, or
1631        // PUBLISH_STATE_NOTIFY." The last is new in draft-20.
1632        0x09 => {
1633            matches!(message, M::SubscribeOk | M::Publish | M::RequestOk | M::PublishStateNotify)
1634        }
1635        // Section 9.20.6 FILL TIMEOUT: it "MAY appear in a FETCH message, or
1636        // inside a FILL_PARAMETERS parameter (see Section 9.20.16) in a
1637        // SUBSCRIBE or REQUEST_UPDATE (for a subscription), where it applies to
1638        // the fill fetch stream." The nested half is not a scope this table can
1639        // answer — a nested parameter is not in the enclosing message at all,
1640        // per Section 9.20.16 — so it is enforced by
1641        // [`decode_fill_parameters`] against Table 6 instead.
1642        0x0A => matches!(message, M::Fetch),
1643        // Section 9.20.19 FORWARD: "It MAY appear in SUBSCRIBE, REQUEST_UPDATE
1644        // (for a subscription or a SUBSCRIBE_TRACKS request), PUBLISH,
1645        // SUBSCRIBE_TRACKS and PUBLISH_STATE_NOTIFY." Draft-19 listed
1646        // PUBLISH_OK and had no PUBLISH_STATE_NOTIFY.
1647        0x10 => matches!(
1648            message,
1649            M::Subscribe
1650                | M::RequestUpdate
1651                | M::Publish
1652                | M::SubscribeTracks
1653                | M::PublishStateNotify
1654        ),
1655        // Section 9.20.8 SUBSCRIBER PRIORITY: "It MAY appear in a SUBSCRIBE,
1656        // PUBLISH, FETCH, or REQUEST_UPDATE (for a subscription or FETCH)."
1657        // Draft-19 said PUBLISH_OK where this says PUBLISH.
1658        0x20 => matches!(message, M::Subscribe | M::Publish | M::Fetch | M::RequestUpdate),
1659        // Section 9.20.10 LOCATION FILTER: "The LOCATION_FILTER parameter
1660        // (Parameter Type 0x21) MAY appear in a FETCH, SUBSCRIBE, PUBLISH,
1661        // REQUEST_UPDATE (for a subscription) or PUBLISH_STATE_NOTIFY
1662        // message." Draft-20 opened the same definition with a sentence of its
1663        // own, "uses length-prefixed encoding", which draft-21 dropped; the
1664        // `Length` field is still in the structure and the wire is unchanged.
1665        // Draft-19 admitted SUBSCRIBE, PUBLISH_OK and REQUEST_UPDATE
1666        // and no FETCH, because a draft-19 FETCH carried its range in the
1667        // message instead. SUBSCRIBE_TRACKS is admitted on top of that list by
1668        // Section 9.18.1's closing sentence, as for FILL_PARAMETERS below.
1669        0x21 => matches!(
1670            message,
1671            M::Fetch
1672                | M::Subscribe
1673                | M::Publish
1674                | M::RequestUpdate
1675                | M::PublishStateNotify
1676                | M::SubscribeTracks
1677        ),
1678        // Section 9.20.9 GROUP ORDER: "It MAY appear in a SUBSCRIBE, PUBLISH,
1679        // SUBSCRIBE_TRACKS, or FETCH, or inside a FILL_PARAMETERS parameter".
1680        // The nested half is Table 6's, as for FILL_TIMEOUT above.
1681        0x22 => matches!(message, M::Subscribe | M::Publish | M::SubscribeTracks | M::Fetch),
1682        // Section 9.20.16 FILL_PARAMETERS, new in draft-20: it "MAY appear in a
1683        // SUBSCRIBE or REQUEST_UPDATE (for a subscription) message."
1684        //
1685        // SUBSCRIBE_TRACKS is admitted on top of those two by Section 9.18.1's
1686        // closing sentence, which tells the subscriber it "can specify a
1687        // Location Filter and optionally include FILL_PARAMETERS" there; see
1688        // the doc comment above for why that sentence wins over this list.
1689        //
1690        // FETCH is refused, and that is the case the corpus pins: a fill fetch
1691        // stream is something a subscription opens, and a FETCH already is one.
1692        0x23 => matches!(message, M::Subscribe | M::RequestUpdate | M::SubscribeTracks),
1693        // Section 3.3.2: "All other filter parameters MAY appear multiple times
1694        // in a FETCH, SUBSCRIBE, SUBSCRIBE_TRACKS, or REQUEST_UPDATE (on a
1695        // subscription, from the subscriber only) message." SUBGROUP_FILTER
1696        // (Section 9.20.11), OBJECTID_FILTER (9.20.12), PRIORITY_FILTER
1697        // (9.20.13) and OBJECT_PROPERTY_FILTER (9.20.14) are those four.
1698        // Draft-19's sentence also listed PUBLISH_OK.
1699        0x25..=0x28 => {
1700            matches!(message, M::Fetch | M::Subscribe | M::SubscribeTracks | M::RequestUpdate)
1701        }
1702        // Section 3.3.2, of TRACK_PROPERTY_FILTER (Section 9.20.15) alone: it
1703        // "MAY appear multiple times in a SUBSCRIBE_TRACKS message or
1704        // REQUEST_UPDATE for it". It selects tracks rather than objects, which
1705        // is why it is the one filter a SUBSCRIBE may not carry and the one
1706        // Table 6 keeps out of FILL_PARAMETERS.
1707        0x29 => matches!(message, M::SubscribeTracks | M::RequestUpdate),
1708        // Section 9.20.20 NEW GROUP REQUEST: "It MAY appear in SUBSCRIBE or
1709        // REQUEST_UPDATE for a subscription." Draft-19 also listed PUBLISH_OK.
1710        0x32 => matches!(message, M::Subscribe | M::RequestUpdate),
1711        // Section 9.20.21 TRACK_NAMESPACE_PREFIX: "It MAY appear in
1712        // REQUEST_UPDATE for a SUBSCRIBE_NAMESPACE or SUBSCRIBE_TRACKS
1713        // request." The two named there are the request being updated, not two
1714        // more places the parameter may be written.
1715        0x34 => matches!(message, M::RequestUpdate),
1716        // Section 9.20.22 INCLUDE_PROPERTIES, new in draft-20: "It MAY appear
1717        // in SUBSCRIBE, TRACK_STATUS, FETCH or SUBSCRIBE_TRACKS."
1718        0x35 => matches!(message, M::Subscribe | M::TrackStatus | M::Fetch | M::SubscribeTracks),
1719        _ => true,
1720    }
1721}
1722
1723/// Refuse a message carrying a Message Parameter its own definition does not
1724/// place there.
1725///
1726/// Section 9.20.1 answers this with a close, which the drafts below do not.
1727/// Draft-16 Section 9.2.2, and drafts 07 through 15 under the older name
1728/// Version Specific Parameters, end the same sentence "it MUST be ignored" —
1729/// so this check belongs to drafts 17 and later and to no draft before them.
1730///
1731/// Applied on both sides. A parameter outside its scope is one the peer must
1732/// close the session over, so writing one is a way to end a session rather than
1733/// a way to ask for anything.
1734fn check_parameter_scope(message: &ControlMessage) -> Result<(), CodecError> {
1735    let parameters = match message {
1736        ControlMessage::RequestOk(m) => &m.parameters,
1737        ControlMessage::Subscribe(m) => &m.parameters,
1738        ControlMessage::SubscribeOk(m) => &m.parameters,
1739        ControlMessage::RequestUpdate(m) => &m.parameters,
1740        ControlMessage::Publish(m) => &m.parameters,
1741        ControlMessage::PublishStateNotify(m) => &m.parameters,
1742        ControlMessage::PublishNamespace(m) => &m.parameters,
1743        ControlMessage::SubscribeNamespace(m) => &m.parameters,
1744        ControlMessage::SubscribeTracks(m) => &m.parameters,
1745        ControlMessage::TrackStatus(m) => &m.parameters,
1746        ControlMessage::Fetch(m) => &m.parameters,
1747        ControlMessage::FetchOk(m) => &m.parameters,
1748        // No Message Parameters field. SETUP is named here rather than left to
1749        // a wildcard because the draft says why it can never have one: Section
1750        // 9.20.1 notes that "since Setup Options use a separate namespace, it
1751        // is impossible for Message Parameters to appear in Setup messages",
1752        // and this codec keeps the two namespaces in separate fields.
1753        ControlMessage::Setup(_)
1754        | ControlMessage::GoAway(_)
1755        | ControlMessage::RequestError(_)
1756        | ControlMessage::PublishDone(_)
1757        | ControlMessage::Namespace(_)
1758        | ControlMessage::NamespaceDone(_)
1759        | ControlMessage::PublishSkipped(_) => return Ok(()),
1760    };
1761
1762    let message_type = message.message_type();
1763    for parameter in parameters {
1764        let key = parameter.key.into_inner();
1765        if !parameter_in_scope(key, message_type) {
1766            return Err(CodecError::ParameterOutOfScope { key, message_type: message_type.id() });
1767        }
1768    }
1769    Ok(())
1770}
1771
1772impl ControlMessage {
1773    pub fn encode(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
1774        check_discriminators(self)?;
1775        check_parameter_scope(self)?;
1776        let mut body = Vec::with_capacity(256);
1777        self.encode_body(&mut body)?;
1778
1779        if body.len() > MAX_MESSAGE_LENGTH {
1780            return Err(CodecError::MessageTooLong(body.len()));
1781        }
1782
1783        let msg_type = self.message_type();
1784        VarInt::from_usize(msg_type.id() as usize).encode_moqt::<Wire>(buf);
1785        // Draft-21: 16-bit length (big-endian)
1786        buf.put_u16(body.len() as u16);
1787        buf.put_slice(&body);
1788        Ok(())
1789    }
1790
1791    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
1792        let type_id = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
1793        let msg_type =
1794            MessageType::from_id(type_id).ok_or(CodecError::UnknownMessageType(type_id))?;
1795        // Draft-21: 16-bit length (big-endian)
1796        if buf.remaining() < 2 {
1797            return Err(CodecError::UnexpectedEnd);
1798        }
1799        let body_len = buf.get_u16() as usize;
1800        if buf.remaining() < body_len {
1801            return Err(CodecError::UnexpectedEnd);
1802        }
1803        let body_bytes = buf.copy_to_bytes(body_len);
1804        let mut body = &body_bytes[..];
1805        let msg = match Self::decode_body(msg_type, &mut body) {
1806            Ok(msg) => msg,
1807            // The fields wanted more bytes than the Length allowed. This buffer
1808            // is already bounded by that Length, so running out inside it cannot
1809            // mean the message is still arriving - which is what the same error
1810            // means everywhere else, and why a reader loops on it rather than
1811            // closing. Here there is nothing left to arrive.
1812            Err(
1813                CodecError::UnexpectedEnd
1814                | CodecError::Kvp(crate::kvp::KvpError::UnexpectedEnd)
1815                | CodecError::Kvp(crate::kvp::KvpError::VarInt(
1816                    crate::varint::VarIntError::UnexpectedEnd,
1817                ))
1818                | CodecError::VarInt(crate::varint::VarIntError::UnexpectedEnd),
1819            ) => {
1820                return Err(CodecError::ControlMessageLengthMismatch {
1821                    declared: body_len,
1822                    detail: "its fields ran past the end",
1823                });
1824            }
1825            Err(e) => return Err(e),
1826        };
1827        check_parameter_scope(&msg)?;
1828        // Draft-21 Section 9: "If the length does not match the length of the
1829        // Message Body, the receiver MUST close the session with a
1830        // PROTOCOL_VIOLATION." A body parser that stops short leaves bytes
1831        // here; without this the surplus is discarded and a truncated or
1832        // mis-framed field looks like a well-formed message.
1833        if body.has_remaining() {
1834            return Err(CodecError::ControlMessageLengthMismatch {
1835                declared: body_len,
1836                detail: "its fields left bytes unread",
1837            });
1838        }
1839        Ok(msg)
1840    }
1841
1842    fn encode_body(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
1843        match self {
1844            ControlMessage::Setup(m) => {
1845                encode_setup_options(&m.options, buf)?;
1846            }
1847            ControlMessage::GoAway(m) => {
1848                if m.new_session_uri.len() > MAX_GOAWAY_URI_LENGTH {
1849                    return Err(CodecError::GoAwayUriTooLong);
1850                }
1851                VarInt::from_usize(m.new_session_uri.len()).encode_moqt::<Wire>(buf);
1852                buf.put_slice(&m.new_session_uri);
1853                m.timeout.encode_moqt::<Wire>(buf);
1854            }
1855            ControlMessage::RequestOk(m) => {
1856                encode_parameters(&m.parameters, buf)?;
1857                encode_track_properties(&m.track_properties, buf)?;
1858            }
1859            ControlMessage::RequestError(m) => {
1860                if m.reason_phrase.len() > MAX_REASON_PHRASE_LENGTH {
1861                    return Err(CodecError::ReasonPhraseTooLong);
1862                }
1863                m.error_code.encode_moqt::<Wire>(buf);
1864                m.retry_interval.encode_moqt::<Wire>(buf);
1865                VarInt::from_usize(m.reason_phrase.len()).encode_moqt::<Wire>(buf);
1866                buf.put_slice(&m.reason_phrase);
1867                if let Some(r) = &m.redirect {
1868                    r.track_namespace.validate_moqt()?;
1869                    check_full_track_name(&r.track_namespace, &r.track_name)?;
1870                    VarInt::from_usize(r.connect_uri.len()).encode_moqt::<Wire>(buf);
1871                    buf.put_slice(&r.connect_uri);
1872                    r.track_namespace.encode_moqt::<Wire>(buf);
1873                    VarInt::from_usize(r.track_name.len()).encode_moqt::<Wire>(buf);
1874                    buf.put_slice(&r.track_name);
1875                }
1876            }
1877            ControlMessage::Subscribe(m) => {
1878                m.track_namespace.validate_moqt()?;
1879                check_full_track_name(&m.track_namespace, &m.track_name)?;
1880                m.request_id.encode_moqt::<Wire>(buf);
1881                m.track_namespace.encode_moqt::<Wire>(buf);
1882                VarInt::from_usize(m.track_name.len()).encode_moqt::<Wire>(buf);
1883                buf.put_slice(&m.track_name);
1884                encode_parameters(&m.parameters, buf)?;
1885            }
1886            ControlMessage::SubscribeOk(m) => {
1887                m.track_alias.encode_moqt::<Wire>(buf);
1888                encode_parameters(&m.parameters, buf)?;
1889                encode_track_properties(&m.track_properties, buf)?;
1890            }
1891            ControlMessage::RequestUpdate(m) => {
1892                m.request_id.encode_moqt::<Wire>(buf);
1893                encode_parameters(&m.parameters, buf)?;
1894            }
1895            ControlMessage::Publish(m) => {
1896                m.track_namespace.validate_moqt()?;
1897                check_full_track_name(&m.track_namespace, &m.track_name)?;
1898                m.request_id.encode_moqt::<Wire>(buf);
1899                m.track_namespace.encode_moqt::<Wire>(buf);
1900                VarInt::from_usize(m.track_name.len()).encode_moqt::<Wire>(buf);
1901                buf.put_slice(&m.track_name);
1902                m.track_alias.encode_moqt::<Wire>(buf);
1903                encode_parameters(&m.parameters, buf)?;
1904                encode_track_properties(&m.track_properties, buf)?;
1905            }
1906            // Section 9.10, Figure 14: parameter count, then parameters,
1907            // and nothing else. No Request ID — the subscription's stream is
1908            // what identifies the message.
1909            ControlMessage::PublishStateNotify(m) => {
1910                encode_parameters(&m.parameters, buf)?;
1911            }
1912            ControlMessage::PublishDone(m) => {
1913                if m.reason_phrase.len() > MAX_REASON_PHRASE_LENGTH {
1914                    return Err(CodecError::ReasonPhraseTooLong);
1915                }
1916                m.status_code.encode_moqt::<Wire>(buf);
1917                m.stream_count.encode_moqt::<Wire>(buf);
1918                VarInt::from_usize(m.reason_phrase.len()).encode_moqt::<Wire>(buf);
1919                buf.put_slice(&m.reason_phrase);
1920            }
1921            ControlMessage::PublishNamespace(m) => {
1922                m.track_namespace.validate_moqt()?;
1923                m.request_id.encode_moqt::<Wire>(buf);
1924                m.track_namespace.encode_moqt::<Wire>(buf);
1925                encode_parameters(&m.parameters, buf)?;
1926            }
1927            ControlMessage::Namespace(m) => {
1928                m.namespace_suffix.validate_moqt()?;
1929                m.namespace_suffix.encode_moqt::<Wire>(buf);
1930            }
1931            ControlMessage::NamespaceDone(m) => {
1932                m.namespace_suffix.validate_moqt()?;
1933                m.namespace_suffix.encode_moqt::<Wire>(buf);
1934            }
1935            ControlMessage::SubscribeNamespace(m) => {
1936                m.namespace_prefix.validate_moqt()?;
1937                m.request_id.encode_moqt::<Wire>(buf);
1938                m.namespace_prefix.encode_moqt::<Wire>(buf);
1939                encode_parameters(&m.parameters, buf)?;
1940            }
1941            ControlMessage::SubscribeTracks(m) => {
1942                m.namespace_prefix.validate_moqt()?;
1943                m.request_id.encode_moqt::<Wire>(buf);
1944                m.namespace_prefix.encode_moqt::<Wire>(buf);
1945                encode_parameters(&m.parameters, buf)?;
1946            }
1947            ControlMessage::TrackStatus(m) => {
1948                m.track_namespace.validate_moqt()?;
1949                check_full_track_name(&m.track_namespace, &m.track_name)?;
1950                m.request_id.encode_moqt::<Wire>(buf);
1951                m.track_namespace.encode_moqt::<Wire>(buf);
1952                VarInt::from_usize(m.track_name.len()).encode_moqt::<Wire>(buf);
1953                buf.put_slice(&m.track_name);
1954                encode_parameters(&m.parameters, buf)?;
1955            }
1956            // Section 9.11, Figure 15. Byte-identical to SUBSCRIBE above:
1957            // no Fetch Type, no inline range, and the namespace and name where
1958            // draft-19 put them inside its Standalone Fetch.
1959            ControlMessage::Fetch(m) => {
1960                m.track_namespace.validate_moqt()?;
1961                check_full_track_name(&m.track_namespace, &m.track_name)?;
1962                m.request_id.encode_moqt::<Wire>(buf);
1963                m.track_namespace.encode_moqt::<Wire>(buf);
1964                VarInt::from_usize(m.track_name.len()).encode_moqt::<Wire>(buf);
1965                buf.put_slice(&m.track_name);
1966                encode_parameters(&m.parameters, buf)?;
1967            }
1968            ControlMessage::FetchOk(m) => {
1969                buf.put_u8(m.end_of_track);
1970                m.end_group.encode_moqt::<Wire>(buf);
1971                m.end_object.encode_moqt::<Wire>(buf);
1972                encode_parameters(&m.parameters, buf)?;
1973                encode_track_properties(&m.track_properties, buf)?;
1974            }
1975            ControlMessage::PublishSkipped(m) => {
1976                m.namespace_suffix.validate_moqt()?;
1977                check_full_track_name(&m.namespace_suffix, &m.track_name)?;
1978                m.namespace_suffix.encode_moqt::<Wire>(buf);
1979                VarInt::from_usize(m.track_name.len()).encode_moqt::<Wire>(buf);
1980                buf.put_slice(&m.track_name);
1981            }
1982        }
1983        Ok(())
1984    }
1985
1986    fn decode_body(msg_type: MessageType, buf: &mut impl Buf) -> Result<Self, CodecError> {
1987        match msg_type {
1988            MessageType::Setup => {
1989                let options = decode_setup_options(buf)?;
1990                Ok(ControlMessage::Setup(Setup { options }))
1991            }
1992            MessageType::GoAway => {
1993                let uri_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
1994                // Draft-21 Section 9.2: an endpoint that receives a New
1995                // Session URI Length above the maximum MUST close the session
1996                // with a PROTOCOL_VIOLATION. Checked here as well as on encode
1997                // so an oversized URI never reaches the application.
1998                if uri_len > MAX_GOAWAY_URI_LENGTH {
1999                    return Err(CodecError::GoAwayUriTooLong);
2000                }
2001                let uri = read_bytes(buf, uri_len)?;
2002                let timeout = VarInt::decode_moqt::<Wire>(buf)?;
2003                Ok(ControlMessage::GoAway(GoAway { new_session_uri: uri, timeout }))
2004            }
2005            MessageType::RequestOk => {
2006                let parameters = decode_parameters(buf)?;
2007                let track_properties = decode_track_properties(buf)?;
2008                Ok(ControlMessage::RequestOk(RequestOk { parameters, track_properties }))
2009            }
2010            MessageType::RequestError => {
2011                let error_code = VarInt::decode_moqt::<Wire>(buf)?;
2012                let retry_interval = VarInt::decode_moqt::<Wire>(buf)?;
2013                let reason_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
2014                // Draft-21 Section 8.5: a received reason phrase length above
2015                // the maximum MUST close the session with a PROTOCOL_VIOLATION.
2016                if reason_len > MAX_REASON_PHRASE_LENGTH {
2017                    return Err(CodecError::ReasonPhraseTooLong);
2018                }
2019                let reason_phrase = read_bytes(buf, reason_len)?;
2020                let redirect = if error_code.into_inner() == request_error_codes::REDIRECT {
2021                    let uri_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
2022                    let connect_uri = read_bytes(buf, uri_len)?;
2023                    let track_namespace = TrackNamespace::decode_allow_empty_moqt::<Wire>(buf)?;
2024                    let name_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
2025                    let track_name = read_bytes(buf, name_len)?;
2026                    check_full_track_name(&track_namespace, &track_name)?;
2027                    Some(Redirect { connect_uri, track_namespace, track_name })
2028                } else {
2029                    None
2030                };
2031                Ok(ControlMessage::RequestError(RequestError {
2032                    error_code,
2033                    retry_interval,
2034                    reason_phrase,
2035                    redirect,
2036                }))
2037            }
2038            MessageType::Subscribe => {
2039                let request_id = VarInt::decode_moqt::<Wire>(buf)?;
2040                let track_namespace = TrackNamespace::decode_moqt::<Wire>(buf)?;
2041                let tn_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
2042                let track_name = read_bytes(buf, tn_len)?;
2043                check_full_track_name(&track_namespace, &track_name)?;
2044                let parameters = decode_parameters(buf)?;
2045                Ok(ControlMessage::Subscribe(Subscribe {
2046                    request_id,
2047                    track_namespace,
2048                    track_name,
2049                    parameters,
2050                }))
2051            }
2052            MessageType::SubscribeOk => {
2053                let track_alias = VarInt::decode_moqt::<Wire>(buf)?;
2054                let parameters = decode_parameters(buf)?;
2055                let track_properties = decode_track_properties(buf)?;
2056                Ok(ControlMessage::SubscribeOk(SubscribeOk {
2057                    track_alias,
2058                    parameters,
2059                    track_properties,
2060                }))
2061            }
2062            MessageType::RequestUpdate => {
2063                let request_id = VarInt::decode_moqt::<Wire>(buf)?;
2064                let parameters = decode_parameters(buf)?;
2065                Ok(ControlMessage::RequestUpdate(RequestUpdate { request_id, parameters }))
2066            }
2067            MessageType::Publish => {
2068                let request_id = VarInt::decode_moqt::<Wire>(buf)?;
2069                let track_namespace = TrackNamespace::decode_moqt::<Wire>(buf)?;
2070                let tn_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
2071                let track_name = read_bytes(buf, tn_len)?;
2072                let track_alias = VarInt::decode_moqt::<Wire>(buf)?;
2073                check_full_track_name(&track_namespace, &track_name)?;
2074                let parameters = decode_parameters(buf)?;
2075                let track_properties = decode_track_properties(buf)?;
2076                Ok(ControlMessage::Publish(Publish {
2077                    request_id,
2078                    track_namespace,
2079                    track_name,
2080                    track_alias,
2081                    parameters,
2082                    track_properties,
2083                }))
2084            }
2085            MessageType::PublishStateNotify => {
2086                let parameters = decode_parameters(buf)?;
2087                Ok(ControlMessage::PublishStateNotify(PublishStateNotify { parameters }))
2088            }
2089            MessageType::PublishDone => {
2090                // The Status Code is carried up whatever it is. Section 13
2091                // forbids closing the session over an unknown one and requires
2092                // it to be read as INTERNAL_ERROR, so refusing the frame here
2093                // would take a choice away from the caller that the draft gives
2094                // it. See [`PublishDone::status_code`].
2095                let status_code = VarInt::decode_moqt::<Wire>(buf)?;
2096                let stream_count = VarInt::decode_moqt::<Wire>(buf)?;
2097                let reason_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
2098                // Draft-21 Section 8.5, same bound as REQUEST_ERROR above.
2099                if reason_len > MAX_REASON_PHRASE_LENGTH {
2100                    return Err(CodecError::ReasonPhraseTooLong);
2101                }
2102                let reason_phrase = read_bytes(buf, reason_len)?;
2103                Ok(ControlMessage::PublishDone(PublishDone {
2104                    status_code,
2105                    stream_count,
2106                    reason_phrase,
2107                }))
2108            }
2109            MessageType::PublishNamespace => {
2110                let request_id = VarInt::decode_moqt::<Wire>(buf)?;
2111                let track_namespace = TrackNamespace::decode_moqt::<Wire>(buf)?;
2112                let parameters = decode_parameters(buf)?;
2113                Ok(ControlMessage::PublishNamespace(PublishNamespace {
2114                    request_id,
2115                    track_namespace,
2116                    parameters,
2117                }))
2118            }
2119            MessageType::Namespace => {
2120                let namespace_suffix = TrackNamespace::decode_allow_empty_moqt::<Wire>(buf)?;
2121                Ok(ControlMessage::Namespace(Namespace { namespace_suffix }))
2122            }
2123            MessageType::NamespaceDone => {
2124                let namespace_suffix = TrackNamespace::decode_allow_empty_moqt::<Wire>(buf)?;
2125                Ok(ControlMessage::NamespaceDone(NamespaceDone { namespace_suffix }))
2126            }
2127            MessageType::SubscribeNamespace => {
2128                let request_id = VarInt::decode_moqt::<Wire>(buf)?;
2129                let namespace_prefix = TrackNamespace::decode_allow_empty_moqt::<Wire>(buf)?;
2130                let parameters = decode_parameters(buf)?;
2131                Ok(ControlMessage::SubscribeNamespace(SubscribeNamespace {
2132                    request_id,
2133                    namespace_prefix,
2134                    parameters,
2135                }))
2136            }
2137            MessageType::SubscribeTracks => {
2138                let request_id = VarInt::decode_moqt::<Wire>(buf)?;
2139                let namespace_prefix = TrackNamespace::decode_allow_empty_moqt::<Wire>(buf)?;
2140                let parameters = decode_parameters(buf)?;
2141                Ok(ControlMessage::SubscribeTracks(SubscribeTracks {
2142                    request_id,
2143                    namespace_prefix,
2144                    parameters,
2145                }))
2146            }
2147            MessageType::TrackStatus => {
2148                let request_id = VarInt::decode_moqt::<Wire>(buf)?;
2149                let track_namespace = TrackNamespace::decode_moqt::<Wire>(buf)?;
2150                let tn_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
2151                let track_name = read_bytes(buf, tn_len)?;
2152                check_full_track_name(&track_namespace, &track_name)?;
2153                let parameters = decode_parameters(buf)?;
2154                Ok(ControlMessage::TrackStatus(TrackStatus {
2155                    request_id,
2156                    track_namespace,
2157                    track_name,
2158                    parameters,
2159                }))
2160            }
2161            MessageType::Fetch => {
2162                let request_id = VarInt::decode_moqt::<Wire>(buf)?;
2163                let track_namespace = TrackNamespace::decode_moqt::<Wire>(buf)?;
2164                let tn_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
2165                let track_name = read_bytes(buf, tn_len)?;
2166                check_full_track_name(&track_namespace, &track_name)?;
2167                let parameters = decode_parameters(buf)?;
2168                Ok(ControlMessage::Fetch(Fetch {
2169                    request_id,
2170                    track_namespace,
2171                    track_name,
2172                    parameters,
2173                }))
2174            }
2175            MessageType::FetchOk => {
2176                if buf.remaining() < 1 {
2177                    return Err(CodecError::UnexpectedEnd);
2178                }
2179                let end_of_track = buf.get_u8();
2180                let end_group = VarInt::decode_moqt::<Wire>(buf)?;
2181                let end_object = VarInt::decode_moqt::<Wire>(buf)?;
2182                let parameters = decode_parameters(buf)?;
2183                let track_properties = decode_track_properties(buf)?;
2184                Ok(ControlMessage::FetchOk(FetchOk {
2185                    end_of_track,
2186                    end_group,
2187                    end_object,
2188                    parameters,
2189                    track_properties,
2190                }))
2191            }
2192            MessageType::PublishSkipped => {
2193                let namespace_suffix = TrackNamespace::decode_allow_empty_moqt::<Wire>(buf)?;
2194                let tn_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
2195                let track_name = read_bytes(buf, tn_len)?;
2196                check_full_track_name(&namespace_suffix, &track_name)?;
2197                Ok(ControlMessage::PublishSkipped(PublishSkipped { namespace_suffix, track_name }))
2198            }
2199        }
2200    }
2201
2202    pub fn message_type(&self) -> MessageType {
2203        match self {
2204            ControlMessage::Setup(_) => MessageType::Setup,
2205            ControlMessage::GoAway(_) => MessageType::GoAway,
2206            ControlMessage::RequestOk(_) => MessageType::RequestOk,
2207            ControlMessage::RequestError(_) => MessageType::RequestError,
2208            ControlMessage::Subscribe(_) => MessageType::Subscribe,
2209            ControlMessage::SubscribeOk(_) => MessageType::SubscribeOk,
2210            ControlMessage::RequestUpdate(_) => MessageType::RequestUpdate,
2211            ControlMessage::Publish(_) => MessageType::Publish,
2212            ControlMessage::PublishStateNotify(_) => MessageType::PublishStateNotify,
2213            ControlMessage::PublishDone(_) => MessageType::PublishDone,
2214            ControlMessage::PublishNamespace(_) => MessageType::PublishNamespace,
2215            ControlMessage::Namespace(_) => MessageType::Namespace,
2216            ControlMessage::NamespaceDone(_) => MessageType::NamespaceDone,
2217            ControlMessage::SubscribeNamespace(_) => MessageType::SubscribeNamespace,
2218            ControlMessage::SubscribeTracks(_) => MessageType::SubscribeTracks,
2219            ControlMessage::TrackStatus(_) => MessageType::TrackStatus,
2220            ControlMessage::Fetch(_) => MessageType::Fetch,
2221            ControlMessage::FetchOk(_) => MessageType::FetchOk,
2222            ControlMessage::PublishSkipped(_) => MessageType::PublishSkipped,
2223        }
2224    }
2225}
2226
2227#[cfg(test)]
2228mod tests {
2229    use super::*;
2230
2231    /// Frame `body` as a draft-21 control message of `type_id`, declaring
2232    /// `declared_len` rather than the body's real length. Used to build the
2233    /// mismatched frame the length rule is about.
2234    fn frame_with_declared_len(type_id: u64, declared_len: u16, body: &[u8]) -> Vec<u8> {
2235        let mut out = Vec::new();
2236        VarInt::from_u64_moqt(type_id).encode_moqt::<Wire>(&mut out);
2237        out.put_u16(declared_len);
2238        out.put_slice(body);
2239        out
2240    }
2241
2242    fn frame(type_id: u64, body: &[u8]) -> Vec<u8> {
2243        frame_with_declared_len(type_id, body.len() as u16, body)
2244    }
2245
2246    /// A SUBSCRIBE body: request id 1, namespace ("a"), track name "b", and
2247    /// `params` already encoded.
2248    fn subscribe_body(params: &[u8]) -> Vec<u8> {
2249        let mut body = vec![0x01, 0x01, 0x01, b'a', 0x01, b'b'];
2250        body.extend_from_slice(params);
2251        body
2252    }
2253
2254    /// Draft-21 Section 9: "If the length does not match the length of the
2255    /// Message Body, the receiver MUST close the session with a
2256    /// PROTOCOL_VIOLATION."
2257    ///
2258    /// Fails if `decode` parses this SUBSCRIBE and lets the two surplus bytes
2259    /// vanish.
2260    #[test]
2261    fn a_message_body_shorter_than_the_declared_length_is_refused() {
2262        let body = subscribe_body(&[0x00]);
2263        let mut junked = body.clone();
2264        junked.extend_from_slice(&[0xff, 0xff]);
2265        let bytes = frame_with_declared_len(0x03, (body.len() + 2) as u16, &junked);
2266
2267        let mut buf = &bytes[..];
2268        assert_eq!(
2269            ControlMessage::decode(&mut buf),
2270            Err(CodecError::ControlMessageLengthMismatch {
2271                declared: (body.len() + 2),
2272                detail: "its fields left bytes unread",
2273            })
2274        );
2275
2276        // The same body with an honest length still decodes, so the guard
2277        // rejects the mismatch and not the message.
2278        let honest = frame(0x03, &body);
2279        let mut buf = &honest[..];
2280        assert!(ControlMessage::decode(&mut buf).is_ok());
2281    }
2282
2283    /// Draft-21 Section 8.5: "The reason phrase length has a maximum value of
2284    /// 1024 bytes. If an endpoint receives a length exceeding the maximum, it
2285    /// MUST close the session with a PROTOCOL_VIOLATION".
2286    ///
2287    /// Fails if the decoder hands the 2000-byte phrase to the application.
2288    #[test]
2289    fn an_over_long_reason_phrase_is_refused_on_decode() {
2290        for (type_id, prefix) in [(0x05u64, vec![0x01, 0x00]), (0x0B, vec![0x01, 0x00])] {
2291            let mut body = prefix;
2292            let over = MAX_REASON_PHRASE_LENGTH + 976;
2293            VarInt::from_usize(over).encode_moqt::<Wire>(&mut body);
2294            body.extend(std::iter::repeat_n(b'x', over));
2295            let bytes = frame(type_id, &body);
2296
2297            let mut buf = &bytes[..];
2298            assert_eq!(
2299                ControlMessage::decode(&mut buf),
2300                Err(CodecError::ReasonPhraseTooLong),
2301                "message type 0x{type_id:x}"
2302            );
2303        }
2304    }
2305
2306    /// Draft-21 Section 9.2: "The maximum length of the New Session URI is
2307    /// 8,192 bytes. If an endpoint receives a length exceeding the maximum, it
2308    /// MUST close the session with a PROTOCOL_VIOLATION."
2309    ///
2310    /// Fails if the oversized URI reaches the application, where a migrating
2311    /// endpoint would follow it.
2312    #[test]
2313    fn an_over_long_goaway_uri_is_refused_on_decode() {
2314        let over = MAX_GOAWAY_URI_LENGTH + 808;
2315        let mut body = Vec::new();
2316        VarInt::from_usize(over).encode_moqt::<Wire>(&mut body);
2317        body.extend(std::iter::repeat_n(b'u', over));
2318        body.push(0x00); // timeout
2319        let bytes = frame(0x10, &body);
2320
2321        let mut buf = &bytes[..];
2322        assert_eq!(ControlMessage::decode(&mut buf), Err(CodecError::GoAwayUriTooLong));
2323    }
2324
2325    /// Draft-21 Section 9.20.9 (GROUP_ORDER): "The allowed values are Ascending
2326    /// (0x1) or Descending (0x2). If an endpoint receives a value outside this
2327    /// range, it MUST close the session with PROTOCOL_VIOLATION." Section
2328    /// 9.20.18 says the same of FORWARD with the values 0 and 1.
2329    ///
2330    /// Fails if the out-of-range byte is handed up as an ordinary parameter.
2331    #[test]
2332    fn a_uint8_parameter_outside_its_range_is_refused() {
2333        // key, rejected value, accepted value
2334        let cases = [(0x22u8, 7u8, 2u8), (0x10, 9, 1)];
2335        for (key, bad, good) in cases {
2336            let bytes = frame(0x03, &subscribe_body(&[0x01, key, bad]));
2337            let mut buf = &bytes[..];
2338            assert_eq!(
2339                ControlMessage::decode(&mut buf),
2340                Err(CodecError::ParameterValueOutOfRange { key: key as u64, value: bad as u64 }),
2341                "parameter 0x{key:x} value {bad}"
2342            );
2343
2344            let bytes = frame(0x03, &subscribe_body(&[0x01, key, good]));
2345            let mut buf = &bytes[..];
2346            assert!(
2347                ControlMessage::decode(&mut buf).is_ok(),
2348                "parameter 0x{key:x} value {good} should still decode"
2349            );
2350        }
2351    }
2352
2353    /// SUBSCRIBER_PRIORITY (0x20) is a uint8 with no restricted range, so it
2354    /// must keep accepting the whole 0-255 span. This is the negative half of
2355    /// the range check: a table that over-reached would fail here.
2356    #[test]
2357    fn subscriber_priority_still_accepts_the_whole_byte_range() {
2358        for value in [0u8, 1, 2, 128, 255] {
2359            let bytes = frame(0x03, &subscribe_body(&[0x01, 0x20, value]));
2360            let mut buf = &bytes[..];
2361            assert!(ControlMessage::decode(&mut buf).is_ok(), "priority {value}");
2362        }
2363    }
2364
2365    fn param(key: u64, value: &[u8]) -> KeyValuePair {
2366        KeyValuePair { key: VarInt::from_u64_moqt(key), value: KvpValue::Bytes(value.to_vec()) }
2367    }
2368
2369    /// Draft-21 Section 9.20.18: "The LARGEST_OBJECT parameter (Parameter Type
2370    /// 0x9) is a Location." Section 9.20 defines Location as "Two consecutive
2371    /// varints (Group, Object)" — the value carries no length of its own.
2372    ///
2373    /// The frame below is built from the draft rather than from this encoder:
2374    /// REQUEST_OK, four body bytes, one parameter, type delta `0x09`, then the
2375    /// two varints `0x0a` and `0x03` for Location (10, 3). A length-prefixed
2376    /// spelling would need a fifth byte.
2377    ///
2378    /// Fails if `0x09` is read as Length-prefixed: the decoder then takes the
2379    /// group varint `0x0a` as a value length of 10 and runs off the end of a
2380    /// four-byte body. [`a_location_does_not_eat_the_block_that_follows_it`]
2381    /// guards the same misreading.
2382    #[test]
2383    fn largest_object_is_two_bare_varints() {
2384        let body = [0x01, 0x09, 0x0a, 0x03];
2385        let bytes = frame(0x07, &body);
2386
2387        let msg = ControlMessage::decode(&mut &bytes[..]).expect("spec-correct frame must decode");
2388        let ControlMessage::RequestOk(ok) = &msg else {
2389            panic!("expected REQUEST_OK, got {msg:?}")
2390        };
2391        assert_eq!(ok.parameters, vec![param(0x09, &[0x0a, 0x03])]);
2392        assert!(ok.track_properties.is_empty(), "the four body bytes are all parameter");
2393
2394        let mut out = Vec::new();
2395        msg.encode(&mut out).expect("re-encode");
2396        assert_eq!(out, bytes, "the value must go back out as the two bare varints it came in as");
2397    }
2398
2399    /// A Location value the encoder was handed but the decoder could not read
2400    /// back is refused on the way out, not written.
2401    ///
2402    /// LARGEST_OBJECT carries no length of its own — that is the whole point
2403    /// of the encoding — so `encode_parameters` writes its bytes verbatim. A
2404    /// value built in memory rather than decoded is under no obligation to be
2405    /// two varints, and without this check the codec answers `Ok(())` and puts
2406    /// a frame on the wire that `ControlMessage::decode` then refuses. One
2407    /// varint short and one varint long are the two ways to get it wrong.
2408    #[test]
2409    fn a_location_value_that_is_not_two_varints_is_refused_on_encode() {
2410        for (label, value) in
2411            [("one varint", vec![0x0a]), ("three varints", vec![0x0a, 0x03, 0x05])]
2412        {
2413            let msg = ControlMessage::RequestOk(RequestOk {
2414                parameters: vec![param(0x09, &value)],
2415                track_properties: Vec::new(),
2416            });
2417            let mut out = Vec::new();
2418            assert!(
2419                msg.encode(&mut out).is_err(),
2420                "LARGEST_OBJECT of {label} must not encode: the decoder cannot read it back"
2421            );
2422        }
2423
2424        // The well-formed value still goes out, so the check refuses the
2425        // malformed case and not the encoding itself.
2426        let msg = ControlMessage::RequestOk(RequestOk {
2427            parameters: vec![param(0x09, &[0x0a, 0x03])],
2428            track_properties: Vec::new(),
2429        });
2430        let mut out = Vec::new();
2431        msg.encode(&mut out).expect("a Location of exactly two varints must still encode");
2432        ControlMessage::decode(&mut &out[..]).expect("and must decode back");
2433    }
2434
2435    /// The same Location read through a message that carries other fields
2436    /// after it, so a stray length byte cannot hide in a trailing block.
2437    ///
2438    /// SUBSCRIBE_OK is track alias `0x05`, then the parameters, then the track
2439    /// properties. With LARGEST_OBJECT (10, 3) and one property
2440    /// (OBJECT_DELIVERY_TIMEOUT, type `0x02`, 5000ms as the two-byte varint
2441    /// `0x93 0x88`), the body is `05 01 09 0a 03 02 93 88`.
2442    #[test]
2443    fn a_location_does_not_eat_the_block_that_follows_it() {
2444        let body = [0x05, 0x01, 0x09, 0x0a, 0x03, 0x02, 0x93, 0x88];
2445        let bytes = frame(0x04, &body);
2446
2447        let msg = ControlMessage::decode(&mut &bytes[..]).expect("spec-correct frame must decode");
2448        let ControlMessage::SubscribeOk(ok) = &msg else {
2449            panic!("expected SUBSCRIBE_OK, got {msg:?}")
2450        };
2451        assert_eq!(ok.parameters, vec![param(0x09, &[0x0a, 0x03])]);
2452        assert_eq!(
2453            ok.track_properties,
2454            vec![KeyValuePair {
2455                key: VarInt::from_u64_moqt(0x02),
2456                value: KvpValue::Varint(VarInt::from_u64_moqt(5000)),
2457            }]
2458        );
2459
2460        let mut out = Vec::new();
2461        msg.encode(&mut out).expect("re-encode");
2462        assert_eq!(out, bytes);
2463    }
2464
2465    /// Draft-21 Section 9.20.21: the TRACK_NAMESPACE_PREFIX parameter
2466    /// (Parameter Type 0x34) "uses the Track Namespace encoding described in
2467    /// Section 8.7" — a varint field count followed by that many
2468    /// length-prefixed fields, and nothing in front of it. That encoding is not
2469    /// one of the four Section 9.20 lists, so it cannot be assumed to be
2470    /// Length-prefixed by default.
2471    ///
2472    /// The frame below is built from Section 8.7: REQUEST_UPDATE for request
2473    /// `7`, one parameter, type delta `0x34`, then the namespace ("live",
2474    /// "sports") as `02 04 "live" 06 "sports"`. Sixteen body bytes; a
2475    /// length-prefixed spelling would need a seventeenth for the outer length.
2476    ///
2477    /// Fails if `0x34` is read as Length-prefixed: the field count `0x02` is
2478    /// then taken as an outer length of two bytes, leaving eleven bytes of
2479    /// namespace unread, which the draft-21 Section 9 body-length check refuses.
2480    /// That check is not a safety net here. Where the surplus lands inside the
2481    /// declared body — as in
2482    /// [`an_empty_track_namespace_prefix_is_one_zero_byte`], whose namespace is
2483    /// one byte long — the misread is silent, and that test catches it.
2484    #[test]
2485    fn track_namespace_prefix_is_a_bare_track_namespace() {
2486        let namespace: Vec<u8> = [&[0x02, 0x04][..], b"live", &[0x06][..], b"sports"].concat();
2487        assert_eq!(namespace.len(), 13);
2488
2489        let body: Vec<u8> = [&[0x07, 0x01, 0x34][..], &namespace].concat();
2490        assert_eq!(body.len(), 16);
2491        let bytes = frame(0x02, &body);
2492
2493        let msg = ControlMessage::decode(&mut &bytes[..]).expect("spec-correct frame must decode");
2494        let ControlMessage::RequestUpdate(update) = &msg else {
2495            panic!("expected REQUEST_UPDATE, got {msg:?}")
2496        };
2497        assert_eq!(update.request_id.into_inner(), 7);
2498        assert_eq!(update.parameters, vec![param(0x34, &namespace)]);
2499
2500        let mut out = Vec::new();
2501        msg.encode(&mut out).expect("re-encode");
2502        assert_eq!(out, bytes, "no outer length may appear in front of the Track Namespace");
2503    }
2504
2505    /// An empty prefix is a legal Track Namespace: Section 2.4.1 puts one at
2506    /// "between 0 and 32 Track Namespace Fields". On the wire that is the
2507    /// single byte `0x00`, and it must not be confused with a length-prefixed
2508    /// value of zero bytes.
2509    #[test]
2510    fn an_empty_track_namespace_prefix_is_one_zero_byte() {
2511        let body = [0x07, 0x01, 0x34, 0x00];
2512        let bytes = frame(0x02, &body);
2513
2514        let msg = ControlMessage::decode(&mut &bytes[..]).expect("empty prefix must decode");
2515        let ControlMessage::RequestUpdate(update) = &msg else {
2516            panic!("expected REQUEST_UPDATE, got {msg:?}")
2517        };
2518        assert_eq!(update.parameters, vec![param(0x34, &[0x00])]);
2519
2520        let mut out = Vec::new();
2521        msg.encode(&mut out).expect("re-encode");
2522        assert_eq!(out, bytes);
2523    }
2524}