Skip to main content

moqtap_codec/draft19/
message.rs

1//! Draft-19 control message encoding and decoding.
2//!
3//! Key differences from draft-18:
4//! - `Request ID` field removed from GOAWAY entirely; the control-stream and
5//!   request-stream forms are now identical.
6//! - New Range Filter parameters (length-prefixed): SUBGROUP_FILTER (0x25),
7//!   OBJECTID_FILTER (0x26), PRIORITY_FILTER (0x27), OBJECT_PROPERTY_FILTER
8//!   (0x28) and TRACK_PROPERTY_FILTER (0x29).
9//! - New Setup Options MAX_FILTER_RANGES (0x06) and MAX_REQUEST_UPDATES (0x08);
10//!   both are even KVP types carrying a varint value.
11//! - GROUP_ORDER (0x22) moves from PUBLISH_OK to SUBSCRIBE_TRACKS (the wire
12//!   encoding of the parameter is unchanged).
13//! - PUBLISH_BLOCKED renamed to PUBLISH_SKIPPED (still type 0x0F; wire
14//!   identical).
15//! - SUBSCRIPTION_FILTER renamed to LOCATION_FILTER (still parameter 0x21).
16//! - REQUEST_ERROR adds CONFLICTING_FILTERS (0x35) and INVALID_FILTER (0x36);
17//!   DUPLICATE_SUBSCRIPTION (0x19) is removed.
18//! - The framing field after Message Length is named Message Body (draft-19
19//!   Section 10, Figure 3); earlier drafts called it Message Payload. The
20//!   change is editorial, so the bytes are unchanged, but this module uses the
21//!   new name.
22
23use crate::auth_token::{AuthorizationToken, AUTH_TOKEN_PARAMETER};
24use crate::error::MAX_FULL_TRACK_NAME_LENGTH;
25pub use crate::error::{
26    CodecError, MAX_GOAWAY_URI_LENGTH, MAX_MESSAGE_LENGTH, MAX_NAMESPACE_TUPLE_SIZE,
27    MAX_REASON_PHRASE_LENGTH,
28};
29use crate::kvp::{KeyValuePair, KvpError, KvpValue, MAX_KVP_VALUE_LEN};
30use crate::subscription_filter::{SubscriptionFilter, SUBSCRIPTION_FILTER_PARAMETER};
31use crate::types::check_location_range;
32use crate::types::*;
33use crate::varint::{Moqt18 as Wire, VarInt};
34use bytes::{Buf, BufMut};
35
36// ============================================================
37// Parameter encoding helpers for draft-19
38// ============================================================
39
40/// How a parameter value is encoded on the wire.
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42enum ParamEncoding {
43    /// Bare varint.
44    Varint,
45    /// Single byte (uint8).
46    Uint8,
47    /// Two consecutive varints (group, object).
48    Location,
49    /// Length-prefixed bytes.
50    LengthPrefixed,
51    /// A Track Namespace as defined in draft-19 Section 2.4.1: a varint field
52    /// count followed by that many length-prefixed fields.
53    ///
54    /// Not one of the four value encodings draft-19 Section 10.2 lists. A
55    /// parameter definition is free to name an encoding from elsewhere in the
56    /// document, and TRACK_NAMESPACE_PREFIX does exactly that; the field count
57    /// is the only length the wire carries.
58    TrackNamespaceValue,
59}
60
61fn param_encoding(key: u64) -> Option<ParamEncoding> {
62    match key {
63        // 0x02 = OBJECT_DELIVERY_TIMEOUT (renamed from DELIVERY_TIMEOUT)
64        // 0x04 = RENDEZVOUS_TIMEOUT (draft-19 Section 10.2.6). Not
65        //        MAX_CACHE_DURATION: that is Property Type 0x04 in the
66        //        separate Properties registry (Section 15.8), a different
67        //        namespace that happens to reuse the number.
68        // 0x06 = SUBGROUP_DELIVERY_TIMEOUT (new in draft-18)
69        // 0x08 = EXPIRES
70        // 0x0A = FILL_TIMEOUT (new in draft-18, FETCH only)
71        // 0x32 = NEW_GROUP_REQUEST
72        0x02 | 0x04 | 0x06 | 0x08 | 0x0A | 0x32 => Some(ParamEncoding::Varint),
73        // 0x10 = FORWARD, 0x20 = SUBSCRIBER_PRIORITY, 0x22 = GROUP_ORDER
74        0x10 | 0x20 | 0x22 => Some(ParamEncoding::Uint8),
75        // 0x09 = LARGEST_OBJECT. Draft-19 Section 10.2.16: "The LARGEST_OBJECT
76        //        parameter (Parameter Type 0x9) is a Location." A Location is
77        //        two consecutive varints (Section 10.2), with no length ahead
78        //        of them.
79        0x09 => Some(ParamEncoding::Location),
80        // 0x34 = TRACK_NAMESPACE_PREFIX. Section 10.2.19: it "uses the Track
81        //        Namespace encoding described in Section 2.4.1".
82        0x34 => Some(ParamEncoding::TrackNamespaceValue),
83        // 0x03 = AUTHORIZATION_TOKEN
84        // 0x21 = LOCATION_FILTER (renamed from SUBSCRIPTION_FILTER)
85        // 0x25 = SUBGROUP_FILTER, 0x26 = OBJECTID_FILTER, 0x27 = PRIORITY_FILTER,
86        // 0x28 = OBJECT_PROPERTY_FILTER, 0x29 = TRACK_PROPERTY_FILTER
87        //        (Range Filters, new in draft-19)
88        0x03 | 0x21 | 0x25 | 0x26 | 0x27 | 0x28 | 0x29 => Some(ParamEncoding::LengthPrefixed),
89        _ => None,
90    }
91}
92
93/// Whether `value` is inside the range draft-19 allows for a uint8-valued
94/// parameter.
95///
96/// Two of the three uint8 parameters restrict their range and say the receiver
97/// MUST close the session with PROTOCOL_VIOLATION on anything outside it:
98/// GROUP_ORDER allows only Ascending (0x1) and Descending (0x2) (Section
99/// 10.2.8), and FORWARD allows only 0 and 1 (Section 10.2.17).
100/// SUBSCRIBER_PRIORITY (Section 10.2.7) uses the whole 0-255 range, so it has
101/// no entry here.
102///
103/// Range-checking on decode is what makes the values usable: an application
104/// that tests `group_order == 2` for descending would otherwise treat 7 as
105/// neither ascending nor descending and carry on.
106fn uint8_value_in_range(key: u64, value: u8) -> bool {
107    match key {
108        // FORWARD (0x10)
109        0x10 => value <= 1,
110        // GROUP_ORDER (0x22)
111        0x22 => value == 1 || value == 2,
112        _ => true,
113    }
114}
115
116/// The one parameter type draft-19 lets a message carry more than once.
117///
118/// Section 10.2.2: "The AUTHORIZATION TOKEN parameter MAY be repeated within a
119/// message as long as the combination of Token Type and Token Value are unique
120/// after resolving any aliases." Every other type is subject to the blanket rule
121/// in Section 10.2.
122const AUTHORIZATION_TOKEN: u64 = 0x03;
123
124/// Add a delta to the previous delta-encoded key.
125///
126/// Draft-19 Section 1.4.3: "The previous Type value plus the Delta Type MUST NOT
127/// be greater than 2^64 - 1. If a Delta Type is received that would be too
128/// large, the Session MUST be closed with a PROTOCOL_VIOLATION." MoQT varints
129/// span the whole 64-bit range, so a peer can drive the sum past the end: a
130/// debug build panicked on the addition and a release build wrapped the key and
131/// reported the parameter under a type its sender never wrote.
132fn add_delta(prev_key: u64, delta: u64) -> Result<u64, CodecError> {
133    prev_key.checked_add(delta).ok_or(CodecError::KeyDeltaOverflow(prev_key, delta))
134}
135
136/// Hold a namespace-plus-name pair to the Full Track Name cap.
137///
138/// Draft-19 Section 2.4.1: "The maximum total length of a Full Track Name is
139/// 4,096 bytes. The length of a Full Track Name is computed as the sum of the
140/// Track Namespace Field Length fields and the Track Name Length field... If an
141/// endpoint receives a Track Namespace or a Full Track Name exceeding 4,096
142/// bytes, it MUST close the session with a PROTOCOL_VIOLATION."
143///
144/// The namespace half of that sentence is enforced inside the namespace decoder,
145/// which is the only place that sees a namespace with no name beside it. This is
146/// the other half, and it has to live where the two are decoded together: a
147/// namespace at 4,000 bytes and a name at 500 are each legal alone.
148fn check_full_track_name(namespace: &TrackNamespace, track_name: &[u8]) -> Result<(), CodecError> {
149    let total = namespace.field_bytes_len().saturating_add(track_name.len());
150    if total > MAX_FULL_TRACK_NAME_LENGTH {
151        return Err(CodecError::TrackNameTooLong);
152    }
153    Ok(())
154}
155
156/// Hold every AUTHORIZATION TOKEN parameter to the Token structure it names.
157///
158/// Section 10.2.2: "If the Token structure cannot be decoded, the receiver
159/// MUST close the Session with KEY_VALUE_FORMATTING_ERROR." That is the answer
160/// Section 1.4.3 gives for any Type whose value does not match the
161/// serialization that Type defines; the Token is the one structure this draft
162/// spells out, and the only parameter value in it that is more than opaque
163/// bytes.
164///
165/// Both namespaces carry the type on this draft, and both reach here.
166///
167/// A type this draft cannot name is left alone. The rule is conditional on the
168/// receiver understanding the Type, and an extension's parameter carries bytes
169/// no rule here describes.
170fn check_authorization_tokens(parameters: &[KeyValuePair]) -> Result<(), CodecError> {
171    for parameter in parameters {
172        let key = parameter.key.into_inner();
173        if key != AUTH_TOKEN_PARAMETER {
174            continue;
175        }
176        match &parameter.value {
177            KvpValue::Bytes(value) => {
178                AuthorizationToken::decode_moqt::<Wire>(key, value)?;
179            }
180            // Unreachable from the decoder, which picks the shape from the
181            // type and finds this one length-prefixed. A caller that built the
182            // pair in memory can still get here, and it is the same rule: the
183            // value is not the serialization the type defines.
184            KvpValue::Varint(_) => {
185                return Err(CodecError::KeyValueFormatting {
186                    key,
187                    detail: "its value is a bare varint where the type defines a Token structure",
188                });
189            }
190        }
191    }
192    Ok(())
193}
194
195/// Hold every LOCATION_FILTER parameter to the filter structure it names.
196///
197/// Section 5.1.2: "An endpoint that receives a filter type other than the above
198/// MUST close the session with PROTOCOL_VIOLATION." Section 10.2.9 defines the
199/// parameter, which this draft renamed from SUBSCRIPTION_FILTER to
200/// LOCATION_FILTER when it added the Range Filters beside it. The number, 0x21,
201/// and the structure are the ones draft-18 had.
202///
203/// Drafts 15 and 16 stated the length rule of this parameter directly, at
204/// draft-16 Section 9.2.2.5 — "If the length of the Subscription Filter does
205/// not match the parameter length, the publisher MUST close the session with
206/// PROTOCOL_VIOLATION." Draft-17 dropped that sentence, and what answers the
207/// same malformation here is the general rule of Section 1.4.3, which names
208/// KEY_VALUE_FORMATTING_ERROR. Same malformation, different code, and the
209/// session table is where the two part.
210///
211/// The End Group is a delta, and this draft states what happens when resolving
212/// it leaves the number space: "the last Group ID to be delivered
213/// will be the Group ID in Start Location plus the End Group Delta. If the
214/// resulting Group ID would be greater than 2^64 - 1, the endpoint MUST close
215/// the session with a PROTOCOL_VIOLATION." That is why the sum is taken here and
216/// not left to the caller — draft-17, which introduced the delta and states no
217/// such sentence, does not take it.
218///
219/// The filter is otherwise decoded and discarded. What is kept is the refusal —
220/// the value stays on the parameter as the bytes that arrived, so a caller reads
221/// it through [`SubscriptionFilter::decode_moqt`] when it wants the filter
222/// rather than the frame.
223fn check_subscription_filters(parameters: &[KeyValuePair]) -> Result<(), CodecError> {
224    for parameter in parameters {
225        if parameter.key.into_inner() != SUBSCRIPTION_FILTER_PARAMETER {
226            continue;
227        }
228        match &parameter.value {
229            KvpValue::Bytes(value) => {
230                SubscriptionFilter::decode_moqt::<Wire>(value)?.last_group()?;
231            }
232            // Unreachable from the decoder, which picks the shape from the type
233            // and finds this one length-prefixed. A caller that built the pair
234            // in memory can still get here, and it is the same rule.
235            KvpValue::Varint(_) => {
236                return Err(CodecError::SubscriptionFilterMalformed {
237                    detail: "its value is a bare varint where the type defines a filter",
238                });
239            }
240        }
241    }
242    Ok(())
243}
244
245/// Decode a count-prefixed list of parameters with delta-encoded types.
246fn decode_parameters(buf: &mut impl Buf) -> Result<Vec<KeyValuePair>, CodecError> {
247    let count = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
248    let mut params = crate::types::reserve_bounded(count, buf);
249    let mut prev_key: u64 = 0;
250
251    for i in 0..count {
252        let delta = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
253        let abs_key = add_delta(prev_key, delta)?;
254        // Types ascend, so a repeat is always a zero delta against the
255        // parameter before it. Draft-19 Section 10.2: "Receivers SHOULD check
256        // that there are no unexpected duplicate parameters and close the
257        // session with PROTOCOL_VIOLATION if found." Downstream code that scans
258        // the list for a key takes whichever copy it meets first, so two
259        // implementations reading one frame can pick opposite values.
260        if i > 0 && delta == 0 && abs_key != AUTHORIZATION_TOKEN {
261            return Err(CodecError::DuplicateParameter(abs_key));
262        }
263        prev_key = abs_key;
264
265        // Section 10.2: "All Message Parameters MUST be defined in the
266        // negotiated version of MOQT or negotiated via Setup Options. An
267        // endpoint that receives an unknown Message Parameter MUST close the
268        // session with PROTOCOL_VIOLATION. Because the receiver has to
269        // understand every Message Parameter, there is no need for a mechanism
270        // to skip unknown parameters." Because unknown parameters
271        // cannot be skipped, the block is bounded by a parameter count rather
272        // than a length.
273        //
274        // The table this consults is the registry's, so a type it cannot name
275        // is one this draft does not define. Reporting it as an ordinary
276        // malformation, which is what it did before, left the rule enforced
277        // against the frame and invisible to the session.
278        let encoding =
279            param_encoding(abs_key).ok_or(CodecError::UnknownMessageParameter(abs_key))?;
280
281        let value = match encoding {
282            ParamEncoding::Varint => {
283                let v = VarInt::decode_moqt::<Wire>(buf)?;
284                KvpValue::Varint(v)
285            }
286            ParamEncoding::Uint8 => {
287                if buf.remaining() < 1 {
288                    return Err(CodecError::UnexpectedEnd);
289                }
290                let byte = buf.get_u8();
291                if !uint8_value_in_range(abs_key, byte) {
292                    return Err(CodecError::ParameterValueOutOfRange {
293                        key: abs_key,
294                        value: byte as u64,
295                    });
296                }
297                KvpValue::Varint(VarInt::from_u64_moqt(byte as u64))
298            }
299            ParamEncoding::Location => {
300                let group = VarInt::decode_moqt::<Wire>(buf)?;
301                let object = VarInt::decode_moqt::<Wire>(buf)?;
302                let mut encoded = Vec::new();
303                group.encode_moqt::<Wire>(&mut encoded);
304                object.encode_moqt::<Wire>(&mut encoded);
305                KvpValue::Bytes(encoded)
306            }
307            ParamEncoding::LengthPrefixed => {
308                let len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
309                let data = read_bytes(buf, len)?;
310                KvpValue::Bytes(data)
311            }
312            ParamEncoding::TrackNamespaceValue => {
313                // A prefix of zero fields is legal: Section 2.4.1 puts a Track
314                // Namespace at "between 0 and 32 Track Namespace Fields", and
315                // an empty prefix matches every namespace.
316                let ns = TrackNamespace::decode_allow_empty_moqt::<Wire>(buf)?;
317                let mut encoded = Vec::new();
318                ns.encode_moqt::<Wire>(&mut encoded);
319                KvpValue::Bytes(encoded)
320            }
321        };
322
323        params.push(KeyValuePair { key: VarInt::from_u64_moqt(abs_key), value });
324    }
325    check_authorization_tokens(&params)?;
326    check_subscription_filters(&params)?;
327    Ok(params)
328}
329
330/// Whether `bytes` is exactly the wire form of a Location — two consecutive
331/// varints and nothing after them.
332///
333/// `decode_parameters` builds this value by reading two varints and
334/// re-serialising them, so every value it produces satisfies this. A value
335/// built in memory need not, and the encode arm writes these bytes verbatim
336/// because a Location carries no length of its own. Without this check a
337/// caller could hand over one varint, or three, and the codec would put a
338/// frame on the wire that its own decoder answers with an error.
339fn is_location_value(bytes: &[u8]) -> bool {
340    let mut buf = bytes;
341    VarInt::decode_moqt::<Wire>(&mut buf).is_ok()
342        && VarInt::decode_moqt::<Wire>(&mut buf).is_ok()
343        && !buf.has_remaining()
344}
345
346/// Whether `bytes` is exactly the wire form of a Track Namespace, with
347/// nothing after it. The same reasoning as [`is_location_value`]: the value
348/// goes out verbatim, so it has to be something this draft can read back.
349fn is_track_namespace_value(bytes: &[u8]) -> bool {
350    let mut buf = bytes;
351    TrackNamespace::decode_allow_empty_moqt::<Wire>(&mut buf).is_ok() && !buf.has_remaining()
352}
353
354/// Encode a count-prefixed list of parameters with delta-encoded types.
355///
356/// Errors with [`CodecError::InvalidField`] on a uint8-valued parameter whose
357/// value [`decode_parameters`] would refuse, so the two directions accept the
358/// same set of frames.
359///
360/// The check is not a mirror added for tidiness. A uint8 parameter's value is
361/// written as one octet, and a value that does not fit one is otherwise
362/// truncated to its low byte: GROUP_ORDER 258 becomes the byte 0x02, which is
363/// Descending — a well-formed frame carrying a value the caller never asked
364/// for, and one no receiver could tell from a genuine Descending. Refusing is
365/// the only outcome that does not silently rewrite the message.
366///
367/// The two structure rules are here for a plainer reason. A value under a type
368/// that defines a structure and is not that structure — a Token, a filter — is
369/// one the receiver must close the session over, so writing it is not a way to
370/// send it; the sender's first sign of trouble would be the session going.
371fn encode_parameters(params: &[KeyValuePair], buf: &mut impl BufMut) -> Result<(), CodecError> {
372    check_authorization_tokens(params)?;
373    check_subscription_filters(params)?;
374    VarInt::from_usize(params.len()).encode_moqt::<Wire>(buf);
375    let mut prev_key: u64 = 0;
376
377    for (i, p) in params.iter().enumerate() {
378        let abs_key = p.key.into_inner();
379        // The delta is a difference, so a descending pair wraps the subtraction
380        // into a nine-byte delta the peer resolves to an unrelated key, and a
381        // repeated type is a frame `decode_parameters` refuses. Both are
382        // refused here so the two directions accept the same set of frames.
383        let delta = abs_key
384            .checked_sub(prev_key)
385            .ok_or(CodecError::ParametersOutOfOrder(prev_key, abs_key))?;
386        if i > 0 && delta == 0 && abs_key != AUTHORIZATION_TOKEN {
387            return Err(CodecError::DuplicateParameter(abs_key));
388        }
389        prev_key = abs_key;
390        VarInt::from_u64_moqt(delta).encode_moqt::<Wire>(buf);
391
392        // The same maximum the decoder below applies, and the same one this
393        // draft's Setup Option encoder has always applied: "The maximum length
394        // of a value is 2^16-1 bytes. If an endpoint receives a length larger
395        // than the maximum, it MUST close the session with a PROTOCOL_VIOLATION."
396        // A value past it is one the peer must end the session over, so writing
397        // it is not a way to send it.
398        //
399        // Hoisted above the shape table rather than repeated inside it: a
400        // Location is bytes as well, and one past the maximum is not a Location.
401        if let KvpValue::Bytes(b) = &p.value {
402            if b.len() > MAX_KVP_VALUE_LEN {
403                return Err(KvpError::ValueTooLong(b.len()).into());
404            }
405        }
406
407        let encoding = param_encoding(abs_key);
408        match (&p.value, encoding) {
409            (KvpValue::Varint(v), Some(ParamEncoding::Varint)) => {
410                v.encode_moqt::<Wire>(buf);
411            }
412            (KvpValue::Varint(v), Some(ParamEncoding::Uint8)) => {
413                let raw = v.into_inner();
414                let byte = u8::try_from(raw).map_err(|_| CodecError::InvalidField)?;
415                if !uint8_value_in_range(abs_key, byte) {
416                    return Err(CodecError::ParameterValueOutOfRange {
417                        key: abs_key,
418                        value: byte as u64,
419                    });
420                }
421                buf.put_u8(byte);
422            }
423            // Both values are already stored in their own wire form — two
424            // varints for a Location, a field count and its fields for a Track
425            // Namespace — so they go out as they are. Adding a length here is
426            // the bug these arms exist to avoid.
427            (KvpValue::Bytes(b), Some(ParamEncoding::Location)) => {
428                if !is_location_value(b) {
429                    return Err(CodecError::InvalidField);
430                }
431                buf.put_slice(b);
432            }
433            (KvpValue::Bytes(b), Some(ParamEncoding::TrackNamespaceValue)) => {
434                if !is_track_namespace_value(b) {
435                    return Err(CodecError::InvalidField);
436                }
437                buf.put_slice(b);
438            }
439            (KvpValue::Bytes(b), Some(ParamEncoding::LengthPrefixed)) => {
440                VarInt::from_usize(b.len()).encode_moqt::<Wire>(buf);
441                buf.put_slice(b);
442            }
443            _ => {
444                // Fallback: encode as KVP even/odd
445                match &p.value {
446                    KvpValue::Varint(v) => v.encode_moqt::<Wire>(buf),
447                    KvpValue::Bytes(b) => {
448                        VarInt::from_usize(b.len()).encode_moqt::<Wire>(buf);
449                        buf.put_slice(b);
450                    }
451                }
452            }
453        }
454    }
455    Ok(())
456}
457
458/// Decode delta-encoded KVPs with even/odd convention (for setup options
459/// and track properties). Read until buffer is exhausted.
460fn decode_kvp_delta(buf: &mut impl Buf) -> Result<Vec<KeyValuePair>, CodecError> {
461    let mut pairs = Vec::new();
462    let mut prev_key: u64 = 0;
463
464    while buf.has_remaining() {
465        let delta = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
466        let abs_key = add_delta(prev_key, delta)?;
467        prev_key = abs_key;
468
469        let value = if abs_key.is_multiple_of(2) {
470            let v = VarInt::decode_moqt::<Wire>(buf)?;
471            KvpValue::Varint(v)
472        } else {
473            let len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
474            // Draft-19 Section 1.4.3: "The maximum length of a value is 2^16-1
475            // bytes. If an endpoint receives a length larger than the maximum,
476            // it MUST close the session with a PROTOCOL_VIOLATION." The
477            // standalone `KeyValuePair::decode` already enforces this; stating
478            // it here too means the two readers of the same wire shape answer
479            // the same way, rather than this one leaning on the caller having
480            // clipped the buffer to a control message first.
481            if len > MAX_KVP_VALUE_LEN {
482                return Err(KvpError::ValueTooLong(len).into());
483            }
484            let data = read_bytes(buf, len)?;
485            KvpValue::Bytes(data)
486        };
487
488        pairs.push(KeyValuePair { key: VarInt::from_u64_moqt(abs_key), value });
489    }
490    Ok(pairs)
491}
492
493/// Encode delta-encoded KVPs with even/odd convention.
494///
495/// Refuses a list that is not in ascending order by type, for the same reason
496/// [`encode_parameters`] does: the delta is a difference, and a descending pair
497/// wraps it into a nine-byte delta the peer resolves to an unrelated key.
498fn encode_kvp_delta(pairs: &[KeyValuePair], buf: &mut impl BufMut) -> Result<(), CodecError> {
499    let mut prev_key: u64 = 0;
500    for p in pairs {
501        let abs_key = p.key.into_inner();
502        let delta = abs_key
503            .checked_sub(prev_key)
504            .ok_or(CodecError::ParametersOutOfOrder(prev_key, abs_key))?;
505        prev_key = abs_key;
506        VarInt::from_u64_moqt(delta).encode_moqt::<Wire>(buf);
507        match &p.value {
508            KvpValue::Varint(v) => v.encode_moqt::<Wire>(buf),
509            KvpValue::Bytes(b) => {
510                if b.len() > MAX_KVP_VALUE_LEN {
511                    return Err(KvpError::ValueTooLong(b.len()).into());
512                }
513                VarInt::from_usize(b.len()).encode_moqt::<Wire>(buf);
514                buf.put_slice(b);
515            }
516        }
517    }
518    Ok(())
519}
520
521/// Immutable Properties, Property Type 0xB.
522///
523/// Section 12.7: Immutable Properties are "a Track or Object Property that
524/// contains a sequence of Key-Value-Pairs (see Figure 2) that are themselves
525/// Track or Object Properties, respectively". The Type is odd, so its value is
526/// length-prefixed bytes, and those bytes are another delta-typed run starting
527/// from 0.
528const IMMUTABLE_PROPERTIES: u64 = 0x0B;
529
530/// Whether `value` is inside the range draft-19 allows for a Track Property
531/// type that restricts one.
532///
533/// Two types do, and each answers anything outside its range with a session
534/// close. DEFAULT_PUBLISHER_GROUP_ORDER (0x22), Section 12.5: "The allowed
535/// values are Ascending (0x1) or Descending (0x2). If an endpoint receives a
536/// value outside this range, it MUST close the session with
537/// PROTOCOL_VIOLATION." DYNAMIC_GROUPS (0x30), Section 12.6: "The allowed
538/// values are 0 or 1... If an endpoint receives a value larger than 1, it MUST
539/// close the session with PROTOCOL_VIOLATION."
540///
541/// Both are Track Properties, so the list they arrive in is the one carried by
542/// a control message rather than the properties on an object.
543///
544/// DEFAULT_PUBLISHER_PRIORITY (0x0E) is not here. Section 12.4 says
545/// "Priorities above 255 are invalid" and stops, where the two above name a
546/// consequence in the next clause. A range stated without one is not a close.
547///
548/// The numbers belong to the Property registry and not the Message Parameter
549/// one. Type 0x22 is GROUP_ORDER as a parameter and
550/// DEFAULT_PUBLISHER_GROUP_ORDER as a property, and the two happen to permit the
551/// same pair of values while meaning different things — one subscriber's
552/// preference against a property of the track. Reading either table for the
553/// other's types would be right by accident here and wrong at the next entry.
554fn track_property_value_in_range(key: u64, value: u64) -> bool {
555    match key {
556        // DEFAULT_PUBLISHER_GROUP_ORDER (0x22)
557        0x22 => value == 1 || value == 2,
558        // DYNAMIC_GROUPS (0x30)
559        0x30 => value <= 1,
560        _ => true,
561    }
562}
563
564/// Refuse a Track Property whose value falls outside the range its type allows,
565/// wherever in the list it is carried.
566///
567/// # Inside Immutable Properties as well as beside them
568///
569/// The list is walked one level down through Immutable Properties, whose
570/// contents Section 12.7 defines as properties themselves. The draft asks for
571/// this in as many words: "When looking for the value of a property, processors
572/// MUST search both the mutable properties and the contents of Immutable
573/// Properties." A check applied only to the outer list is one a peer opts out of
574/// by moving a pair inside the block, and the block is where an Original
575/// Publisher puts what a relay must not rewrite — which is where a track's group
576/// order and dynamic-group support belong.
577///
578/// Bytes under 0xB that do not parse as a Key-Value-Pair run are left alone
579/// rather than refused. Section 12.7 says relays "MAY decode and view the
580/// Properties in the Key-Value-Pairs", which is a permission and not a
581/// requirement, so a block this codec cannot read is carried to the caller
582/// intact instead of ending the session.
583fn check_track_property_values(properties: &[KeyValuePair]) -> Result<(), CodecError> {
584    for property in properties {
585        let key = property.key.into_inner();
586        match &property.value {
587            KvpValue::Varint(value) => {
588                let value = value.into_inner();
589                if !track_property_value_in_range(key, value) {
590                    return Err(CodecError::TrackPropertyValueOutOfRange { key, value });
591                }
592            }
593            KvpValue::Bytes(bytes) if key == IMMUTABLE_PROPERTIES => {
594                let mut inner = &bytes[..];
595                match decode_kvp_delta(&mut inner) {
596                    Ok(nested) => check_track_property_values(&nested)?,
597                    // Not a Key-Value-Pair run. See the note above: reading the
598                    // block is a permission, so one that cannot be read is
599                    // carried rather than refused.
600                    Err(_) => return Ok(()),
601                }
602            }
603            KvpValue::Bytes(_) => {}
604        }
605    }
606    Ok(())
607}
608
609/// Decode the Track Properties that fill the tail of a control message.
610///
611/// [`decode_kvp_delta`] with the Property registry's value rules applied. The
612/// two are separate because that function also reads Setup Options, which are a
613/// third namespace numbering its entries independently of this one.
614fn decode_track_properties(buf: &mut impl Buf) -> Result<Vec<KeyValuePair>, CodecError> {
615    let properties = decode_kvp_delta(buf)?;
616    check_track_property_values(&properties)?;
617    Ok(properties)
618}
619
620/// Encode a control message's Track Properties.
621///
622/// Held to the same value ranges as the decoder. A value this codec refuses to
623/// read is one it must not write: the peer that receives it is required to close
624/// the session, so the sender's first sign of trouble would be the session
625/// going.
626fn encode_track_properties(
627    properties: &[KeyValuePair],
628    buf: &mut impl BufMut,
629) -> Result<(), CodecError> {
630    check_track_property_values(properties)?;
631    encode_kvp_delta(properties, buf)
632}
633
634/// The Setup Option types this draft defines.
635///
636/// Section 10.3.1 assigns PATH, AUTHORIZATION TOKEN, MAX_AUTH_TOKEN_CACHE_SIZE, AUTHORITY,
637/// MAX_FILTER_RANGES, MOQT_IMPLEMENTATION and MAX_REQUEST_UPDATES.
638///
639/// The list exists for one rule and one direction. Section 10.3: "Receivers
640/// MUST allow duplicates of unknown Setup Options." A receiver may therefore
641/// refuse a repeat only of a type it can name, and an option outside this list
642/// is one an extension defined and this codec has no business closing a session
643/// over. Nothing else reads it - unknown options are still decoded and carried,
644/// as "Receivers MUST ignore unrecognized Setup Options" requires.
645const KNOWN_SETUP_OPTIONS: &[u64] = &[0x01, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
646
647/// The one Setup Option whose definition allows more than one instance.
648///
649/// Section 10.3.1.4: "The AUTHORIZATION TOKEN Setup Option (Option Type 0x03)
650/// is functionally equivalent to the AUTHORIZATION TOKEN message parameter...
651/// The endpoint can specify one or more tokens in SETUP that the peer can use to
652/// authorize MOQT session establishment." That is the "unless the option
653/// definition explicitly allows multiple instances" carve-out, and it is the
654/// only one on this draft.
655const REPEATABLE_SETUP_OPTION: u64 = 0x03;
656
657/// Decode the Setup Options of a SETUP message.
658///
659/// Section 10.3: "Senders MUST NOT repeat the same Option Type in a message
660/// unless the option definition explicitly allows multiple instances. Receivers
661/// MUST allow duplicates of unknown Setup Options."
662///
663/// The second sentence is why this is not the mirror of
664/// [`encode_setup_options`]: a repeat of a type this draft names is refused, and
665/// a repeat of any other type is carried. Types ascend and are delta-encoded, so
666/// a repeat is always a zero delta against the option before it.
667fn decode_setup_options(buf: &mut impl Buf) -> Result<Vec<KeyValuePair>, CodecError> {
668    let options = decode_kvp_delta(buf)?;
669    for (i, option) in options.iter().enumerate() {
670        let key = option.key.into_inner();
671        if key == REPEATABLE_SETUP_OPTION || !KNOWN_SETUP_OPTIONS.contains(&key) {
672            continue;
673        }
674        if options[..i].iter().any(|earlier| earlier.key == option.key) {
675            return Err(CodecError::DuplicateParameter(key));
676        }
677    }
678    check_authorization_tokens(&options)?;
679    Ok(options)
680}
681
682/// Encode the Setup Options of a SETUP message.
683///
684/// The sender's half of the same sentence, and it is the wider half: "Senders
685/// MUST NOT repeat the same Option Type in a message" names no exception for
686/// types the sender does not recognise, so every repeat is refused here except
687/// the one the draft allows. A caller holding an option this codec has never
688/// heard of still may not send it twice.
689///
690/// The token is in this namespace as well, and is held to its structure here for
691/// the reason [`encode_parameters`] gives.
692fn encode_setup_options(options: &[KeyValuePair], buf: &mut impl BufMut) -> Result<(), CodecError> {
693    check_authorization_tokens(options)?;
694    for (i, option) in options.iter().enumerate() {
695        if option.key.into_inner() == REPEATABLE_SETUP_OPTION {
696            continue;
697        }
698        if options[..i].iter().any(|earlier| earlier.key == option.key) {
699            return Err(CodecError::DuplicateParameter(option.key.into_inner()));
700        }
701    }
702    encode_kvp_delta(options, buf)
703}
704
705// ============================================================
706// Message Types
707// ============================================================
708
709#[derive(Debug, Clone, Copy, PartialEq, Eq)]
710#[repr(u64)]
711pub enum MessageType {
712    RequestUpdate = 0x02,
713    Subscribe = 0x03,
714    SubscribeOk = 0x04,
715    RequestError = 0x05,
716    PublishNamespace = 0x06,
717    /// REQUEST_OK (0x07). PUBLISH_OK is now an alias of this type.
718    RequestOk = 0x07,
719    Namespace = 0x08,
720    PublishDone = 0x0B,
721    TrackStatus = 0x0D,
722    NamespaceDone = 0x0E,
723    PublishSkipped = 0x0F,
724    GoAway = 0x10,
725    Fetch = 0x16,
726    FetchOk = 0x18,
727    Publish = 0x1D,
728    /// SUBSCRIBE_NAMESPACE (renumbered to 0x50 in draft-18).
729    SubscribeNamespace = 0x50,
730    /// SUBSCRIBE_TRACKS (new message in draft-18).
731    SubscribeTracks = 0x51,
732    Setup = 0x2F00,
733}
734
735impl MessageType {
736    pub fn from_id(id: u64) -> Option<Self> {
737        match id {
738            0x02 => Some(MessageType::RequestUpdate),
739            0x03 => Some(MessageType::Subscribe),
740            0x04 => Some(MessageType::SubscribeOk),
741            0x05 => Some(MessageType::RequestError),
742            0x06 => Some(MessageType::PublishNamespace),
743            0x07 => Some(MessageType::RequestOk),
744            0x08 => Some(MessageType::Namespace),
745            0x0B => Some(MessageType::PublishDone),
746            0x0D => Some(MessageType::TrackStatus),
747            0x0E => Some(MessageType::NamespaceDone),
748            0x0F => Some(MessageType::PublishSkipped),
749            0x10 => Some(MessageType::GoAway),
750            0x16 => Some(MessageType::Fetch),
751            0x18 => Some(MessageType::FetchOk),
752            0x1D => Some(MessageType::Publish),
753            0x50 => Some(MessageType::SubscribeNamespace),
754            0x51 => Some(MessageType::SubscribeTracks),
755            0x2F00 => Some(MessageType::Setup),
756            _ => None,
757        }
758    }
759
760    pub fn id(&self) -> u64 {
761        *self as u64
762    }
763}
764
765// ============================================================
766// Session Lifecycle Messages
767// ============================================================
768
769/// Unified SETUP (0x2F00).
770#[derive(Debug, Clone, PartialEq, Eq)]
771pub struct Setup {
772    pub options: Vec<KeyValuePair>,
773}
774
775/// GOAWAY (0x10). In draft-19 the Request ID field is removed, so the
776/// control-stream and request-stream forms are identical on the wire.
777#[derive(Debug, Clone, PartialEq, Eq)]
778pub struct GoAway {
779    pub new_session_uri: Vec<u8>,
780    pub timeout: VarInt,
781}
782
783// ============================================================
784// Consolidated Response Messages
785// ============================================================
786
787/// REQUEST_OK (0x07). Used as a generic OK response and as the alias for
788/// PUBLISH_OK / REQUEST_UPDATE_OK / TRACK_STATUS_OK / SUBSCRIBE_NAMESPACE_OK
789/// / PUBLISH_NAMESPACE_OK.
790///
791/// `track_properties` is only populated for TRACK_STATUS_OK; for every
792/// other shape it MUST be empty (length implicit from the message length).
793#[derive(Debug, Clone, PartialEq, Eq)]
794pub struct RequestOk {
795    pub parameters: Vec<KeyValuePair>,
796    pub track_properties: Vec<KeyValuePair>,
797}
798
799/// Optional Redirect structure carried in REQUEST_ERROR with code 0x34.
800#[derive(Debug, Clone, PartialEq, Eq)]
801pub struct Redirect {
802    pub connect_uri: Vec<u8>,
803    pub track_namespace: TrackNamespace,
804    pub track_name: Vec<u8>,
805}
806
807/// REQUEST_ERROR (0x05). Adds an optional Redirect structure when
808/// `error_code` is REDIRECT (0x34).
809#[derive(Debug, Clone, PartialEq, Eq)]
810pub struct RequestError {
811    pub error_code: VarInt,
812    pub retry_interval: VarInt,
813    pub reason_phrase: Vec<u8>,
814    pub redirect: Option<Redirect>,
815}
816
817/// REQUEST_ERROR error codes with dedicated meaning.
818///
819/// Note: DUPLICATE_SUBSCRIPTION (0x19) is removed in draft-19, as multiple
820/// concurrent subscriptions per Track are now allowed.
821pub mod request_error_codes {
822    /// A Mandatory Track Property the receiver does not understand.
823    pub const UNSUPPORTED_EXTENSION: u64 = 0x33;
824    /// Response carries a [`super::Redirect`] structure.
825    pub const REDIRECT: u64 = 0x34;
826    /// New in draft-19: SUBSCRIBE_TRACKS filter parameters conflict among too
827    /// many subscribers to aggregate the subscription upstream.
828    pub const CONFLICTING_FILTERS: u64 = 0x35;
829    /// New in draft-19: a Range Filter parameter is invalid or exceeds
830    /// MAX_FILTER_RANGES.
831    pub const INVALID_FILTER: u64 = 0x36;
832}
833
834// ============================================================
835// Subscribe Messages
836// ============================================================
837
838#[derive(Debug, Clone, PartialEq, Eq)]
839pub struct Subscribe {
840    pub request_id: VarInt,
841    pub track_namespace: TrackNamespace,
842    pub track_name: Vec<u8>,
843    pub parameters: Vec<KeyValuePair>,
844}
845
846/// SUBSCRIBE_OK (0x04).
847#[derive(Debug, Clone, PartialEq, Eq)]
848pub struct SubscribeOk {
849    pub track_alias: VarInt,
850    pub parameters: Vec<KeyValuePair>,
851    pub track_properties: Vec<KeyValuePair>,
852}
853
854#[derive(Debug, Clone, PartialEq, Eq)]
855pub struct RequestUpdate {
856    pub request_id: VarInt,
857    pub parameters: Vec<KeyValuePair>,
858}
859
860// ============================================================
861// Publish Messages
862// ============================================================
863
864#[derive(Debug, Clone, PartialEq, Eq)]
865pub struct Publish {
866    pub request_id: VarInt,
867    pub track_namespace: TrackNamespace,
868    pub track_name: Vec<u8>,
869    pub track_alias: VarInt,
870    pub parameters: Vec<KeyValuePair>,
871    pub track_properties: Vec<KeyValuePair>,
872}
873
874/// PUBLISH_DONE (0x0B). Status codes 0x5/0x6 are swapped vs draft-17.
875#[derive(Debug, Clone, PartialEq, Eq)]
876pub struct PublishDone {
877    pub status_code: VarInt,
878    pub stream_count: VarInt,
879    pub reason_phrase: Vec<u8>,
880}
881
882/// Numeric values for the [`PublishDone::status_code`] field.
883pub mod publish_done_codes {
884    /// Draft-18: TOO_FAR_BEHIND is 0x05 (was 0x06 in draft-17).
885    pub const TOO_FAR_BEHIND: u64 = 0x05;
886    /// Draft-18: EXPIRED is 0x06 (was 0x05 in draft-17).
887    pub const EXPIRED: u64 = 0x06;
888}
889
890// ============================================================
891// Publish Namespace Messages
892// ============================================================
893
894#[derive(Debug, Clone, PartialEq, Eq)]
895pub struct PublishNamespace {
896    pub request_id: VarInt,
897    pub track_namespace: TrackNamespace,
898    pub parameters: Vec<KeyValuePair>,
899}
900
901// ============================================================
902// Namespace Messages
903// ============================================================
904
905#[derive(Debug, Clone, PartialEq, Eq)]
906pub struct Namespace {
907    pub namespace_suffix: TrackNamespace,
908}
909
910#[derive(Debug, Clone, PartialEq, Eq)]
911pub struct NamespaceDone {
912    pub namespace_suffix: TrackNamespace,
913}
914
915// ============================================================
916// Subscribe Namespace / Tracks Messages
917// ============================================================
918
919/// SUBSCRIBE_NAMESPACE (0x50). Subscribes to NAMESPACE / NAMESPACE_DONE
920/// advertisements for namespaces matching `namespace_prefix`. The
921/// `subscribe_options` byte from draft-17 is removed; namespace subscriptions
922/// only produce NAMESPACE / NAMESPACE_DONE.
923#[derive(Debug, Clone, PartialEq, Eq)]
924pub struct SubscribeNamespace {
925    pub request_id: VarInt,
926    pub namespace_prefix: TrackNamespace,
927    pub parameters: Vec<KeyValuePair>,
928}
929
930/// SUBSCRIBE_TRACKS (0x51, new in draft-18). Subscribes to PUBLISH messages
931/// for tracks whose namespace matches `namespace_prefix`. Carries the
932/// FORWARD parameter (which previously lived on SUBSCRIBE_NAMESPACE).
933#[derive(Debug, Clone, PartialEq, Eq)]
934pub struct SubscribeTracks {
935    pub request_id: VarInt,
936    pub namespace_prefix: TrackNamespace,
937    pub parameters: Vec<KeyValuePair>,
938}
939
940// ============================================================
941// Track Status Messages
942// ============================================================
943
944#[derive(Debug, Clone, PartialEq, Eq)]
945pub struct TrackStatus {
946    pub request_id: VarInt,
947    pub track_namespace: TrackNamespace,
948    pub track_name: Vec<u8>,
949    pub parameters: Vec<KeyValuePair>,
950}
951
952// ============================================================
953// Fetch Messages
954// ============================================================
955
956#[derive(Debug, Clone, Copy, PartialEq, Eq)]
957#[repr(u64)]
958pub enum FetchType {
959    Standalone = 1,
960    RelativeJoining = 2,
961    AbsoluteJoining = 3,
962}
963
964impl FetchType {
965    pub fn from_u64(v: u64) -> Option<Self> {
966        match v {
967            1 => Some(FetchType::Standalone),
968            2 => Some(FetchType::RelativeJoining),
969            3 => Some(FetchType::AbsoluteJoining),
970            _ => None,
971        }
972    }
973}
974
975#[derive(Debug, Clone, PartialEq, Eq)]
976pub struct Fetch {
977    pub request_id: VarInt,
978    pub fetch_type: FetchType,
979    pub fetch_payload: FetchPayload,
980    pub parameters: Vec<KeyValuePair>,
981}
982
983#[derive(Debug, Clone, PartialEq, Eq)]
984pub enum FetchPayload {
985    Standalone {
986        track_namespace: TrackNamespace,
987        track_name: Vec<u8>,
988        start_group: VarInt,
989        start_object: VarInt,
990        end_group: VarInt,
991        end_object: VarInt,
992    },
993    Joining {
994        joining_request_id: VarInt,
995        joining_start: VarInt,
996    },
997}
998
999/// FETCH_OK (0x18). `end_of_track` is uint8.
1000#[derive(Debug, Clone, PartialEq, Eq)]
1001pub struct FetchOk {
1002    pub end_of_track: u8,
1003    pub end_group: VarInt,
1004    pub end_object: VarInt,
1005    pub parameters: Vec<KeyValuePair>,
1006    pub track_properties: Vec<KeyValuePair>,
1007}
1008
1009// ============================================================
1010// Publish Skipped
1011// ============================================================
1012
1013/// PUBLISH_SKIPPED (0x0F, renamed from PUBLISH_BLOCKED in draft-19; wire
1014/// layout is unchanged).
1015#[derive(Debug, Clone, PartialEq, Eq)]
1016pub struct PublishSkipped {
1017    pub namespace_suffix: TrackNamespace,
1018    pub track_name: Vec<u8>,
1019}
1020
1021// ============================================================
1022// Unified Message Enum
1023// ============================================================
1024
1025#[derive(Debug, Clone, PartialEq, Eq)]
1026pub enum ControlMessage {
1027    Setup(Setup),
1028    GoAway(GoAway),
1029    RequestOk(RequestOk),
1030    RequestError(RequestError),
1031    Subscribe(Subscribe),
1032    SubscribeOk(SubscribeOk),
1033    RequestUpdate(RequestUpdate),
1034    Publish(Publish),
1035    PublishDone(PublishDone),
1036    PublishNamespace(PublishNamespace),
1037    Namespace(Namespace),
1038    NamespaceDone(NamespaceDone),
1039    SubscribeNamespace(SubscribeNamespace),
1040    SubscribeTracks(SubscribeTracks),
1041    TrackStatus(TrackStatus),
1042    Fetch(Fetch),
1043    FetchOk(FetchOk),
1044    PublishSkipped(PublishSkipped),
1045}
1046
1047/// Refuse a FETCH whose range ends before it starts.
1048///
1049/// Section 10.12.3: "Fetch specifies an inclusive range of Objects starting at
1050/// Start Location and ending at End Location. End Location MUST specify the
1051/// same or a larger Location than Start Location for Standalone and Absolute Joining Fetches." A Joining Fetch names
1052/// no explicit range - it is computed from the subscription it joins - so only
1053/// a standalone range is checked here.
1054///
1055/// SUBSCRIBE is not checked here, and needs no check: this draft's
1056/// AbsoluteRange filter carries an End Group Delta measured from the start
1057/// location rather than an absolute End Group, so an end before the start
1058/// has no encoding.
1059///
1060/// Applied on both sides. A range that ends before it starts selects nothing,
1061/// and the peer's only recourse is an error response or a session close, so
1062/// writing one is not a way to ask for anything.
1063fn check_ranges(message: &ControlMessage) -> Result<(), CodecError> {
1064    match message {
1065        ControlMessage::Fetch(m) => match &m.fetch_payload {
1066            FetchPayload::Standalone {
1067                start_group, start_object, end_group, end_object, ..
1068            } => check_location_range(
1069                start_group.into_inner(),
1070                start_object.into_inner(),
1071                end_group.into_inner(),
1072                end_object.into_inner(),
1073            ),
1074            FetchPayload::Joining { .. } => Ok(()),
1075        },
1076        _ => Ok(()),
1077    }
1078}
1079
1080/// Refuse a message whose discriminator disagrees with the fields beside it.
1081///
1082/// Two draft-19 messages carry a field that says which of the following fields
1083/// are on the wire: FETCH's Fetch Type, and REQUEST_ERROR's Error Code, whose
1084/// REDIRECT value (0x34) is what puts the Redirect structure on the wire. This
1085/// codec holds the alternatives in an enum and an `Option`, so a value can say
1086/// one thing in its discriminator and another in its body, and the two sides of
1087/// the codec resolve that differently — the encoder writes whatever the body
1088/// holds, and the decoder reads whatever the discriminator announces.
1089///
1090/// The result is a message that does not survive its own round trip:
1091///
1092/// - A FETCH whose type says Standalone and whose body is a joining pair
1093///   encodes to a joining request id and a joining start where a Track
1094///   Namespace and a Track Name belong, and comes back as a Standalone fetch of
1095///   a track named after two integers — or, more often, as an error, which at
1096///   least is honest. The two joining types share one body shape, so the check
1097///   is between Standalone and everything else rather than one arm per type.
1098/// - A REQUEST_ERROR with code REDIRECT and no Redirect body encodes to a
1099///   message that ends where the decoder expects a Connect URI length, so the
1100///   peer reads the redirect out of whatever follows or runs off the end. The
1101///   mirror case is quieter and no better: a Redirect body under any other
1102///   error code is written out and then skipped by a decoder that was never
1103///   told to look for it, so the sender believes it redirected a peer that
1104///   never saw a redirect.
1105///
1106/// Refusing at the encoder keeps the two readings from ever diverging on the
1107/// wire.
1108fn check_discriminators(message: &ControlMessage) -> Result<(), CodecError> {
1109    match message {
1110        ControlMessage::Fetch(m) => {
1111            let body_is_standalone = matches!(m.fetch_payload, FetchPayload::Standalone { .. });
1112            if body_is_standalone != (m.fetch_type == FetchType::Standalone) {
1113                return Err(CodecError::InvalidField);
1114            }
1115        }
1116        ControlMessage::RequestError(m) => {
1117            let code_is_redirect = m.error_code.into_inner() == request_error_codes::REDIRECT;
1118            if code_is_redirect != m.redirect.is_some() {
1119                return Err(CodecError::InvalidField);
1120            }
1121        }
1122        _ => {}
1123    }
1124    Ok(())
1125}
1126
1127/// Whether draft-19 lets Message Parameter `key` appear in `message`.
1128///
1129/// Section 10.2.1: "Each Message Parameter definition indicates the message
1130/// types in which it can appear. If it appears in some other type of message,
1131/// the receiving endpoint MUST close the connection with a PROTOCOL_VIOLATION."
1132/// One arm per entry in the Message Parameters registry (Section 15.7),
1133/// carrying the message types that entry's own definition names.
1134///
1135/// Three things about this draft the arms below fold in:
1136///
1137/// * Six of the names are one wire type. Section 10.5: "This document uses the
1138///   shorthand PUBLISH_OK, REQUEST_UPDATE_OK, TRACK_STATUS_OK,
1139///   SUBSCRIBE_NAMESPACE_OK, and PUBLISH_NAMESPACE_OK to refer to a REQUEST_OK
1140///   sent in response to the corresponding request type", and the same section
1141///   sends a REQUEST_OK in answer to SUBSCRIBE_TRACKS as well. Which one a
1142///   given REQUEST_OK is depends on the request its Request ID answers, which
1143///   is session state and not in the frame, so each of those names widens the
1144///   same arm and a REQUEST_OK is held to their union.
1145/// * The five Range Filters state their scope in Section 5.1.3 rather than in
1146///   their own subsections: the Track Property filter "MAY appear multiple
1147///   times in a SUBSCRIBE_TRACKS message or REQUEST_UPDATE for it", and "all
1148///   other filter parameters MAY appear multiple times in a FETCH, SUBSCRIBE,
1149///   SUBSCRIBE_TRACKS, PUBLISH_OK, or REQUEST_UPDATE" message. A parameter
1150///   definition is free to state its scope elsewhere, and these do.
1151/// * SUBSCRIBE_TRACKS inherits SUBSCRIBE's whole set. Section 10.19.1: "Any
1152///   Parameter that can be specified on a Subscription (ie: in SUBSCRIBE) is
1153///   valid in SUBSCRIBE_TRACKS, unless otherwise specified." Draft-18 has no
1154///   such sentence, which is why its SUBSCRIBE_TRACKS admits two types and
1155///   this one admits fourteen.
1156///
1157/// FETCH_OK has no arm in the table below, and that is the draft's doing rather
1158/// than an omission here: Section 10.13 gives it a Parameters field and no
1159/// parameter definition names it, so every type this draft defines is "some
1160/// other type of message" there.
1161///
1162/// The table decides scope only. A type this draft does not define has no scope
1163/// to be outside of and is answered by [`CodecError::UnknownMessageParameter`],
1164/// which is why the final arm carries rather than refuses.
1165fn parameter_in_scope(key: u64, message: MessageType) -> bool {
1166    use MessageType as M;
1167    // Section 10.19.1 makes SUBSCRIBE_TRACKS a superset of SUBSCRIBE, so every
1168    // arm admitting one admits the other. The arms spell both out rather than
1169    // wrapping the call, so each still reads against its own sentence.
1170    match key {
1171        // Section 10.2.4 OBJECT_DELIVERY_TIMEOUT: "It MAY appear in a
1172        // PUBLISH_OK, SUBSCRIBE, or REQUEST_UPDATE message."
1173        0x02 => {
1174            matches!(message, M::RequestOk | M::Subscribe | M::RequestUpdate | M::SubscribeTracks)
1175        }
1176        // Section 10.2.2 AUTHORIZATION TOKEN: "It MAY appear in a PUBLISH,
1177        // SUBSCRIBE, REQUEST_UPDATE, SUBSCRIBE_NAMESPACE, SUBSCRIBE_TRACKS,
1178        // PUBLISH_NAMESPACE, TRACK_STATUS or FETCH message."
1179        0x03 => matches!(
1180            message,
1181            M::Publish
1182                | M::Subscribe
1183                | M::RequestUpdate
1184                | M::SubscribeNamespace
1185                | M::SubscribeTracks
1186                | M::PublishNamespace
1187                | M::TrackStatus
1188                | M::Fetch
1189        ),
1190        // Section 10.2.6 RENDEZVOUS TIMEOUT: it "MAY appear in a SUBSCRIBE
1191        // message".
1192        0x04 => matches!(message, M::Subscribe | M::SubscribeTracks),
1193        // Section 10.2.3 SUBGROUP_DELIVERY_TIMEOUT: "It MAY appear in a
1194        // PUBLISH_OK, SUBSCRIBE, or REQUEST_UPDATE message."
1195        0x06 => {
1196            matches!(message, M::RequestOk | M::Subscribe | M::RequestUpdate | M::SubscribeTracks)
1197        }
1198        // Section 10.2.15 EXPIRES: "It MAY appear in SUBSCRIBE_OK, PUBLISH,
1199        // PUBLISH_OK, SUBSCRIBE_NAMESPACE_OK, SUBSCRIBE_TRACKS_OK,
1200        // PUBLISH_NAMESPACE_OK, or REQUEST_UPDATE_OK." Five of those seven are
1201        // a REQUEST_OK.
1202        0x08 => matches!(message, M::SubscribeOk | M::Publish | M::RequestOk),
1203        // Section 10.2.16 LARGEST OBJECT: "It MAY appear in SUBSCRIBE_OK,
1204        // PUBLISH, REQUEST_UPDATE_OK, or TRACK_STATUS_OK."
1205        0x09 => matches!(message, M::SubscribeOk | M::Publish | M::RequestOk),
1206        // Section 10.2.5 FILL TIMEOUT: it "MAY appear in a FETCH message".
1207        0x0A => matches!(message, M::Fetch),
1208        // Section 10.2.17 FORWARD: "It MAY appear in SUBSCRIBE, REQUEST_UPDATE
1209        // (for a subscription), PUBLISH, PUBLISH_OK and SUBSCRIBE_TRACKS."
1210        0x10 => matches!(
1211            message,
1212            M::Subscribe | M::RequestUpdate | M::Publish | M::RequestOk | M::SubscribeTracks
1213        ),
1214        // Section 10.2.7 SUBSCRIBER PRIORITY: "It MAY appear in a SUBSCRIBE,
1215        // FETCH, REQUEST_UPDATE (for a subscription or FETCH), or PUBLISH_OK
1216        // message."
1217        0x20 => matches!(
1218            message,
1219            M::Subscribe | M::Fetch | M::RequestUpdate | M::RequestOk | M::SubscribeTracks
1220        ),
1221        // Section 10.2.9 LOCATION FILTER: "It MAY appear in a SUBSCRIBE,
1222        // PUBLISH_OK or REQUEST_UPDATE (for a subscription) message."
1223        0x21 => {
1224            matches!(message, M::Subscribe | M::RequestOk | M::RequestUpdate | M::SubscribeTracks)
1225        }
1226        // Section 10.2.8 GROUP ORDER: "It MAY appear in a SUBSCRIBE,
1227        // SUBSCRIBE_TRACKS, or FETCH."
1228        0x22 => matches!(message, M::Subscribe | M::SubscribeTracks | M::Fetch),
1229        // Section 5.1.3: "All other filter parameters MAY appear multiple times
1230        // in a FETCH, SUBSCRIBE, SUBSCRIBE_TRACKS, PUBLISH_OK, or
1231        // REQUEST_UPDATE (on a subscription, from the subscriber only)
1232        // message." SUBGROUP_FILTER (Section 10.2.10), OBJECTID_FILTER
1233        // (10.2.11), PRIORITY_FILTER (10.2.12) and OBJECT_PROPERTY_FILTER
1234        // (10.2.13) are those four.
1235        0x25..=0x28 => matches!(
1236            message,
1237            M::Fetch | M::Subscribe | M::SubscribeTracks | M::RequestOk | M::RequestUpdate
1238        ),
1239        // Section 5.1.3, of TRACK_PROPERTY_FILTER (Section 10.2.14) alone: it
1240        // "MAY appear multiple times in a SUBSCRIBE_TRACKS message or
1241        // REQUEST_UPDATE for it". It selects tracks rather than objects, which
1242        // is why it is the one filter a SUBSCRIBE may not carry.
1243        0x29 => matches!(message, M::SubscribeTracks | M::RequestUpdate),
1244        // Section 10.2.18 NEW GROUP REQUEST: "It MAY appear in PUBLISH_OK,
1245        // SUBSCRIBE or REQUEST_UPDATE for a subscription."
1246        0x32 => {
1247            matches!(message, M::RequestOk | M::Subscribe | M::RequestUpdate | M::SubscribeTracks)
1248        }
1249        // Section 10.2.19 TRACK_NAMESPACE_PREFIX: "It MAY appear in
1250        // REQUEST_UPDATE for a SUBSCRIBE_NAMESPACE or SUBSCRIBE_TRACKS
1251        // request." The two named there are the request being updated, not two
1252        // more places the parameter may be written.
1253        0x34 => matches!(message, M::RequestUpdate),
1254        _ => true,
1255    }
1256}
1257
1258/// Refuse a message carrying a Message Parameter its own definition does not
1259/// place there.
1260///
1261/// Section 10.2.1 answers this with a close, which the drafts below do not.
1262/// Draft-16 Section 9.2.2, and drafts 07 through 15 under the older name
1263/// Version Specific Parameters, end the same sentence "it MUST be ignored" —
1264/// so this check belongs to drafts 17, 18 and 19 and to no draft before them.
1265///
1266/// Applied on both sides. A parameter outside its scope is one the peer must
1267/// close the session over, so writing one is a way to end a session rather than
1268/// a way to ask for anything.
1269fn check_parameter_scope(message: &ControlMessage) -> Result<(), CodecError> {
1270    let parameters = match message {
1271        ControlMessage::RequestOk(m) => &m.parameters,
1272        ControlMessage::Subscribe(m) => &m.parameters,
1273        ControlMessage::SubscribeOk(m) => &m.parameters,
1274        ControlMessage::RequestUpdate(m) => &m.parameters,
1275        ControlMessage::Publish(m) => &m.parameters,
1276        ControlMessage::PublishNamespace(m) => &m.parameters,
1277        ControlMessage::SubscribeNamespace(m) => &m.parameters,
1278        ControlMessage::SubscribeTracks(m) => &m.parameters,
1279        ControlMessage::TrackStatus(m) => &m.parameters,
1280        ControlMessage::Fetch(m) => &m.parameters,
1281        ControlMessage::FetchOk(m) => &m.parameters,
1282        // No Message Parameters field. SETUP is named here rather than left to
1283        // a wildcard because the draft says why it can never have one: Section
1284        // 10.2.1 notes that "since Setup Options use a separate namespace, it
1285        // is impossible for Message Parameters to appear in Setup messages",
1286        // and this codec keeps the two namespaces in separate fields.
1287        ControlMessage::Setup(_)
1288        | ControlMessage::GoAway(_)
1289        | ControlMessage::RequestError(_)
1290        | ControlMessage::PublishDone(_)
1291        | ControlMessage::Namespace(_)
1292        | ControlMessage::NamespaceDone(_)
1293        | ControlMessage::PublishSkipped(_) => return Ok(()),
1294    };
1295
1296    let message_type = message.message_type();
1297    for parameter in parameters {
1298        let key = parameter.key.into_inner();
1299        if !parameter_in_scope(key, message_type) {
1300            return Err(CodecError::ParameterOutOfScope { key, message_type: message_type.id() });
1301        }
1302    }
1303    Ok(())
1304}
1305
1306impl ControlMessage {
1307    pub fn encode(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
1308        check_discriminators(self)?;
1309        check_ranges(self)?;
1310        check_parameter_scope(self)?;
1311        let mut body = Vec::with_capacity(256);
1312        self.encode_body(&mut body)?;
1313
1314        if body.len() > MAX_MESSAGE_LENGTH {
1315            return Err(CodecError::MessageTooLong(body.len()));
1316        }
1317
1318        let msg_type = self.message_type();
1319        VarInt::from_usize(msg_type.id() as usize).encode_moqt::<Wire>(buf);
1320        // Draft-19: 16-bit length (big-endian)
1321        buf.put_u16(body.len() as u16);
1322        buf.put_slice(&body);
1323        Ok(())
1324    }
1325
1326    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
1327        let type_id = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
1328        let msg_type =
1329            MessageType::from_id(type_id).ok_or(CodecError::UnknownMessageType(type_id))?;
1330        // Draft-19: 16-bit length (big-endian)
1331        if buf.remaining() < 2 {
1332            return Err(CodecError::UnexpectedEnd);
1333        }
1334        let body_len = buf.get_u16() as usize;
1335        if buf.remaining() < body_len {
1336            return Err(CodecError::UnexpectedEnd);
1337        }
1338        let body_bytes = buf.copy_to_bytes(body_len);
1339        let mut body = &body_bytes[..];
1340        let msg = match Self::decode_body(msg_type, &mut body) {
1341            Ok(msg) => msg,
1342            // The fields wanted more bytes than the Length allowed. This buffer
1343            // is already bounded by that Length, so running out inside it cannot
1344            // mean the message is still arriving - which is what the same error
1345            // means everywhere else, and why a reader loops on it rather than
1346            // closing. Here there is nothing left to arrive.
1347            Err(
1348                CodecError::UnexpectedEnd
1349                | CodecError::Kvp(crate::kvp::KvpError::UnexpectedEnd)
1350                | CodecError::Kvp(crate::kvp::KvpError::VarInt(
1351                    crate::varint::VarIntError::UnexpectedEnd,
1352                ))
1353                | CodecError::VarInt(crate::varint::VarIntError::UnexpectedEnd),
1354            ) => {
1355                return Err(CodecError::ControlMessageLengthMismatch {
1356                    declared: body_len,
1357                    detail: "its fields ran past the end",
1358                });
1359            }
1360            Err(e) => return Err(e),
1361        };
1362        check_ranges(&msg)?;
1363        check_parameter_scope(&msg)?;
1364        // Draft-19 Section 10: "If the length does not match the length of the
1365        // Message Body, the receiver MUST close the session with a
1366        // PROTOCOL_VIOLATION." A body parser that stops short leaves bytes
1367        // here; without this the surplus is discarded and a truncated or
1368        // mis-framed field looks like a well-formed message.
1369        if body.has_remaining() {
1370            return Err(CodecError::ControlMessageLengthMismatch {
1371                declared: body_len,
1372                detail: "its fields left bytes unread",
1373            });
1374        }
1375        Ok(msg)
1376    }
1377
1378    fn encode_body(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
1379        match self {
1380            ControlMessage::Setup(m) => {
1381                encode_setup_options(&m.options, buf)?;
1382            }
1383            ControlMessage::GoAway(m) => {
1384                if m.new_session_uri.len() > MAX_GOAWAY_URI_LENGTH {
1385                    return Err(CodecError::GoAwayUriTooLong);
1386                }
1387                VarInt::from_usize(m.new_session_uri.len()).encode_moqt::<Wire>(buf);
1388                buf.put_slice(&m.new_session_uri);
1389                m.timeout.encode_moqt::<Wire>(buf);
1390            }
1391            ControlMessage::RequestOk(m) => {
1392                encode_parameters(&m.parameters, buf)?;
1393                encode_track_properties(&m.track_properties, buf)?;
1394            }
1395            ControlMessage::RequestError(m) => {
1396                if m.reason_phrase.len() > MAX_REASON_PHRASE_LENGTH {
1397                    return Err(CodecError::ReasonPhraseTooLong);
1398                }
1399                m.error_code.encode_moqt::<Wire>(buf);
1400                m.retry_interval.encode_moqt::<Wire>(buf);
1401                VarInt::from_usize(m.reason_phrase.len()).encode_moqt::<Wire>(buf);
1402                buf.put_slice(&m.reason_phrase);
1403                if let Some(r) = &m.redirect {
1404                    r.track_namespace.validate_moqt()?;
1405                    check_full_track_name(&r.track_namespace, &r.track_name)?;
1406                    VarInt::from_usize(r.connect_uri.len()).encode_moqt::<Wire>(buf);
1407                    buf.put_slice(&r.connect_uri);
1408                    r.track_namespace.encode_moqt::<Wire>(buf);
1409                    VarInt::from_usize(r.track_name.len()).encode_moqt::<Wire>(buf);
1410                    buf.put_slice(&r.track_name);
1411                }
1412            }
1413            ControlMessage::Subscribe(m) => {
1414                m.track_namespace.validate_moqt()?;
1415                check_full_track_name(&m.track_namespace, &m.track_name)?;
1416                m.request_id.encode_moqt::<Wire>(buf);
1417                m.track_namespace.encode_moqt::<Wire>(buf);
1418                VarInt::from_usize(m.track_name.len()).encode_moqt::<Wire>(buf);
1419                buf.put_slice(&m.track_name);
1420                encode_parameters(&m.parameters, buf)?;
1421            }
1422            ControlMessage::SubscribeOk(m) => {
1423                m.track_alias.encode_moqt::<Wire>(buf);
1424                encode_parameters(&m.parameters, buf)?;
1425                encode_track_properties(&m.track_properties, buf)?;
1426            }
1427            ControlMessage::RequestUpdate(m) => {
1428                m.request_id.encode_moqt::<Wire>(buf);
1429                encode_parameters(&m.parameters, buf)?;
1430            }
1431            ControlMessage::Publish(m) => {
1432                m.track_namespace.validate_moqt()?;
1433                check_full_track_name(&m.track_namespace, &m.track_name)?;
1434                m.request_id.encode_moqt::<Wire>(buf);
1435                m.track_namespace.encode_moqt::<Wire>(buf);
1436                VarInt::from_usize(m.track_name.len()).encode_moqt::<Wire>(buf);
1437                buf.put_slice(&m.track_name);
1438                m.track_alias.encode_moqt::<Wire>(buf);
1439                encode_parameters(&m.parameters, buf)?;
1440                encode_track_properties(&m.track_properties, buf)?;
1441            }
1442            ControlMessage::PublishDone(m) => {
1443                if m.reason_phrase.len() > MAX_REASON_PHRASE_LENGTH {
1444                    return Err(CodecError::ReasonPhraseTooLong);
1445                }
1446                m.status_code.encode_moqt::<Wire>(buf);
1447                m.stream_count.encode_moqt::<Wire>(buf);
1448                VarInt::from_usize(m.reason_phrase.len()).encode_moqt::<Wire>(buf);
1449                buf.put_slice(&m.reason_phrase);
1450            }
1451            ControlMessage::PublishNamespace(m) => {
1452                m.track_namespace.validate_moqt()?;
1453                m.request_id.encode_moqt::<Wire>(buf);
1454                m.track_namespace.encode_moqt::<Wire>(buf);
1455                encode_parameters(&m.parameters, buf)?;
1456            }
1457            ControlMessage::Namespace(m) => {
1458                m.namespace_suffix.validate_moqt()?;
1459                m.namespace_suffix.encode_moqt::<Wire>(buf);
1460            }
1461            ControlMessage::NamespaceDone(m) => {
1462                m.namespace_suffix.validate_moqt()?;
1463                m.namespace_suffix.encode_moqt::<Wire>(buf);
1464            }
1465            ControlMessage::SubscribeNamespace(m) => {
1466                m.namespace_prefix.validate_moqt()?;
1467                m.request_id.encode_moqt::<Wire>(buf);
1468                m.namespace_prefix.encode_moqt::<Wire>(buf);
1469                encode_parameters(&m.parameters, buf)?;
1470            }
1471            ControlMessage::SubscribeTracks(m) => {
1472                m.namespace_prefix.validate_moqt()?;
1473                m.request_id.encode_moqt::<Wire>(buf);
1474                m.namespace_prefix.encode_moqt::<Wire>(buf);
1475                encode_parameters(&m.parameters, buf)?;
1476            }
1477            ControlMessage::TrackStatus(m) => {
1478                m.track_namespace.validate_moqt()?;
1479                check_full_track_name(&m.track_namespace, &m.track_name)?;
1480                m.request_id.encode_moqt::<Wire>(buf);
1481                m.track_namespace.encode_moqt::<Wire>(buf);
1482                VarInt::from_usize(m.track_name.len()).encode_moqt::<Wire>(buf);
1483                buf.put_slice(&m.track_name);
1484                encode_parameters(&m.parameters, buf)?;
1485            }
1486            ControlMessage::Fetch(m) => {
1487                m.request_id.encode_moqt::<Wire>(buf);
1488                VarInt::from_usize(m.fetch_type as usize).encode_moqt::<Wire>(buf);
1489                match &m.fetch_payload {
1490                    FetchPayload::Standalone {
1491                        track_namespace,
1492                        track_name,
1493                        start_group,
1494                        start_object,
1495                        end_group,
1496                        end_object,
1497                    } => {
1498                        track_namespace.validate_moqt()?;
1499                        check_full_track_name(track_namespace, track_name)?;
1500                        track_namespace.encode_moqt::<Wire>(buf);
1501                        VarInt::from_usize(track_name.len()).encode_moqt::<Wire>(buf);
1502                        buf.put_slice(track_name);
1503                        start_group.encode_moqt::<Wire>(buf);
1504                        start_object.encode_moqt::<Wire>(buf);
1505                        end_group.encode_moqt::<Wire>(buf);
1506                        end_object.encode_moqt::<Wire>(buf);
1507                    }
1508                    FetchPayload::Joining { joining_request_id, joining_start } => {
1509                        joining_request_id.encode_moqt::<Wire>(buf);
1510                        joining_start.encode_moqt::<Wire>(buf);
1511                    }
1512                }
1513                encode_parameters(&m.parameters, buf)?;
1514            }
1515            ControlMessage::FetchOk(m) => {
1516                buf.put_u8(m.end_of_track);
1517                m.end_group.encode_moqt::<Wire>(buf);
1518                m.end_object.encode_moqt::<Wire>(buf);
1519                encode_parameters(&m.parameters, buf)?;
1520                encode_track_properties(&m.track_properties, buf)?;
1521            }
1522            ControlMessage::PublishSkipped(m) => {
1523                m.namespace_suffix.validate_moqt()?;
1524                check_full_track_name(&m.namespace_suffix, &m.track_name)?;
1525                m.namespace_suffix.encode_moqt::<Wire>(buf);
1526                VarInt::from_usize(m.track_name.len()).encode_moqt::<Wire>(buf);
1527                buf.put_slice(&m.track_name);
1528            }
1529        }
1530        Ok(())
1531    }
1532
1533    fn decode_body(msg_type: MessageType, buf: &mut impl Buf) -> Result<Self, CodecError> {
1534        match msg_type {
1535            MessageType::Setup => {
1536                let options = decode_setup_options(buf)?;
1537                Ok(ControlMessage::Setup(Setup { options }))
1538            }
1539            MessageType::GoAway => {
1540                let uri_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
1541                // Draft-19 Section 10.4: an endpoint that receives a New
1542                // Session URI Length above the maximum MUST close the session
1543                // with a PROTOCOL_VIOLATION. Checked here as well as on encode
1544                // so an oversized URI never reaches the application.
1545                if uri_len > MAX_GOAWAY_URI_LENGTH {
1546                    return Err(CodecError::GoAwayUriTooLong);
1547                }
1548                let uri = read_bytes(buf, uri_len)?;
1549                let timeout = VarInt::decode_moqt::<Wire>(buf)?;
1550                Ok(ControlMessage::GoAway(GoAway { new_session_uri: uri, timeout }))
1551            }
1552            MessageType::RequestOk => {
1553                let parameters = decode_parameters(buf)?;
1554                let track_properties = decode_track_properties(buf)?;
1555                Ok(ControlMessage::RequestOk(RequestOk { parameters, track_properties }))
1556            }
1557            MessageType::RequestError => {
1558                let error_code = VarInt::decode_moqt::<Wire>(buf)?;
1559                let retry_interval = VarInt::decode_moqt::<Wire>(buf)?;
1560                let reason_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
1561                // Draft-19 Section 1.4.4: a received reason phrase length above
1562                // the maximum MUST close the session with a PROTOCOL_VIOLATION.
1563                if reason_len > MAX_REASON_PHRASE_LENGTH {
1564                    return Err(CodecError::ReasonPhraseTooLong);
1565                }
1566                let reason_phrase = read_bytes(buf, reason_len)?;
1567                let redirect = if error_code.into_inner() == request_error_codes::REDIRECT {
1568                    let uri_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
1569                    let connect_uri = read_bytes(buf, uri_len)?;
1570                    let track_namespace = TrackNamespace::decode_allow_empty_moqt::<Wire>(buf)?;
1571                    let name_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
1572                    let track_name = read_bytes(buf, name_len)?;
1573                    check_full_track_name(&track_namespace, &track_name)?;
1574                    Some(Redirect { connect_uri, track_namespace, track_name })
1575                } else {
1576                    None
1577                };
1578                Ok(ControlMessage::RequestError(RequestError {
1579                    error_code,
1580                    retry_interval,
1581                    reason_phrase,
1582                    redirect,
1583                }))
1584            }
1585            MessageType::Subscribe => {
1586                let request_id = VarInt::decode_moqt::<Wire>(buf)?;
1587                let track_namespace = TrackNamespace::decode_moqt::<Wire>(buf)?;
1588                let tn_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
1589                let track_name = read_bytes(buf, tn_len)?;
1590                check_full_track_name(&track_namespace, &track_name)?;
1591                let parameters = decode_parameters(buf)?;
1592                Ok(ControlMessage::Subscribe(Subscribe {
1593                    request_id,
1594                    track_namespace,
1595                    track_name,
1596                    parameters,
1597                }))
1598            }
1599            MessageType::SubscribeOk => {
1600                let track_alias = VarInt::decode_moqt::<Wire>(buf)?;
1601                let parameters = decode_parameters(buf)?;
1602                let track_properties = decode_track_properties(buf)?;
1603                Ok(ControlMessage::SubscribeOk(SubscribeOk {
1604                    track_alias,
1605                    parameters,
1606                    track_properties,
1607                }))
1608            }
1609            MessageType::RequestUpdate => {
1610                let request_id = VarInt::decode_moqt::<Wire>(buf)?;
1611                let parameters = decode_parameters(buf)?;
1612                Ok(ControlMessage::RequestUpdate(RequestUpdate { request_id, parameters }))
1613            }
1614            MessageType::Publish => {
1615                let request_id = VarInt::decode_moqt::<Wire>(buf)?;
1616                let track_namespace = TrackNamespace::decode_moqt::<Wire>(buf)?;
1617                let tn_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
1618                let track_name = read_bytes(buf, tn_len)?;
1619                let track_alias = VarInt::decode_moqt::<Wire>(buf)?;
1620                check_full_track_name(&track_namespace, &track_name)?;
1621                let parameters = decode_parameters(buf)?;
1622                let track_properties = decode_track_properties(buf)?;
1623                Ok(ControlMessage::Publish(Publish {
1624                    request_id,
1625                    track_namespace,
1626                    track_name,
1627                    track_alias,
1628                    parameters,
1629                    track_properties,
1630                }))
1631            }
1632            MessageType::PublishDone => {
1633                let status_code = VarInt::decode_moqt::<Wire>(buf)?;
1634                let stream_count = VarInt::decode_moqt::<Wire>(buf)?;
1635                let reason_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
1636                // Draft-19 Section 1.4.4, same bound as REQUEST_ERROR above.
1637                if reason_len > MAX_REASON_PHRASE_LENGTH {
1638                    return Err(CodecError::ReasonPhraseTooLong);
1639                }
1640                let reason_phrase = read_bytes(buf, reason_len)?;
1641                Ok(ControlMessage::PublishDone(PublishDone {
1642                    status_code,
1643                    stream_count,
1644                    reason_phrase,
1645                }))
1646            }
1647            MessageType::PublishNamespace => {
1648                let request_id = VarInt::decode_moqt::<Wire>(buf)?;
1649                let track_namespace = TrackNamespace::decode_moqt::<Wire>(buf)?;
1650                let parameters = decode_parameters(buf)?;
1651                Ok(ControlMessage::PublishNamespace(PublishNamespace {
1652                    request_id,
1653                    track_namespace,
1654                    parameters,
1655                }))
1656            }
1657            MessageType::Namespace => {
1658                let namespace_suffix = TrackNamespace::decode_allow_empty_moqt::<Wire>(buf)?;
1659                Ok(ControlMessage::Namespace(Namespace { namespace_suffix }))
1660            }
1661            MessageType::NamespaceDone => {
1662                let namespace_suffix = TrackNamespace::decode_allow_empty_moqt::<Wire>(buf)?;
1663                Ok(ControlMessage::NamespaceDone(NamespaceDone { namespace_suffix }))
1664            }
1665            MessageType::SubscribeNamespace => {
1666                let request_id = VarInt::decode_moqt::<Wire>(buf)?;
1667                let namespace_prefix = TrackNamespace::decode_allow_empty_moqt::<Wire>(buf)?;
1668                let parameters = decode_parameters(buf)?;
1669                Ok(ControlMessage::SubscribeNamespace(SubscribeNamespace {
1670                    request_id,
1671                    namespace_prefix,
1672                    parameters,
1673                }))
1674            }
1675            MessageType::SubscribeTracks => {
1676                let request_id = VarInt::decode_moqt::<Wire>(buf)?;
1677                let namespace_prefix = TrackNamespace::decode_allow_empty_moqt::<Wire>(buf)?;
1678                let parameters = decode_parameters(buf)?;
1679                Ok(ControlMessage::SubscribeTracks(SubscribeTracks {
1680                    request_id,
1681                    namespace_prefix,
1682                    parameters,
1683                }))
1684            }
1685            MessageType::TrackStatus => {
1686                let request_id = VarInt::decode_moqt::<Wire>(buf)?;
1687                let track_namespace = TrackNamespace::decode_moqt::<Wire>(buf)?;
1688                let tn_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
1689                let track_name = read_bytes(buf, tn_len)?;
1690                check_full_track_name(&track_namespace, &track_name)?;
1691                let parameters = decode_parameters(buf)?;
1692                Ok(ControlMessage::TrackStatus(TrackStatus {
1693                    request_id,
1694                    track_namespace,
1695                    track_name,
1696                    parameters,
1697                }))
1698            }
1699            MessageType::Fetch => {
1700                let request_id = VarInt::decode_moqt::<Wire>(buf)?;
1701                let fetch_type_val = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
1702                let fetch_type = FetchType::from_u64(fetch_type_val)
1703                    .ok_or(CodecError::InvalidFetchType(fetch_type_val))?;
1704                let fetch_payload = match fetch_type {
1705                    FetchType::Standalone => {
1706                        let track_namespace = TrackNamespace::decode_moqt::<Wire>(buf)?;
1707                        let tn_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
1708                        let track_name = read_bytes(buf, tn_len)?;
1709                        let start_group = VarInt::decode_moqt::<Wire>(buf)?;
1710                        let start_object = VarInt::decode_moqt::<Wire>(buf)?;
1711                        let end_group = VarInt::decode_moqt::<Wire>(buf)?;
1712                        let end_object = VarInt::decode_moqt::<Wire>(buf)?;
1713                        check_full_track_name(&track_namespace, &track_name)?;
1714                        FetchPayload::Standalone {
1715                            track_namespace,
1716                            track_name,
1717                            start_group,
1718                            start_object,
1719                            end_group,
1720                            end_object,
1721                        }
1722                    }
1723                    FetchType::RelativeJoining | FetchType::AbsoluteJoining => {
1724                        let joining_request_id = VarInt::decode_moqt::<Wire>(buf)?;
1725                        let joining_start = VarInt::decode_moqt::<Wire>(buf)?;
1726                        FetchPayload::Joining { joining_request_id, joining_start }
1727                    }
1728                };
1729                let parameters = decode_parameters(buf)?;
1730                Ok(ControlMessage::Fetch(Fetch {
1731                    request_id,
1732                    fetch_type,
1733                    fetch_payload,
1734                    parameters,
1735                }))
1736            }
1737            MessageType::FetchOk => {
1738                if buf.remaining() < 1 {
1739                    return Err(CodecError::UnexpectedEnd);
1740                }
1741                let end_of_track = buf.get_u8();
1742                let end_group = VarInt::decode_moqt::<Wire>(buf)?;
1743                let end_object = VarInt::decode_moqt::<Wire>(buf)?;
1744                let parameters = decode_parameters(buf)?;
1745                let track_properties = decode_track_properties(buf)?;
1746                Ok(ControlMessage::FetchOk(FetchOk {
1747                    end_of_track,
1748                    end_group,
1749                    end_object,
1750                    parameters,
1751                    track_properties,
1752                }))
1753            }
1754            MessageType::PublishSkipped => {
1755                let namespace_suffix = TrackNamespace::decode_allow_empty_moqt::<Wire>(buf)?;
1756                let tn_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
1757                let track_name = read_bytes(buf, tn_len)?;
1758                check_full_track_name(&namespace_suffix, &track_name)?;
1759                Ok(ControlMessage::PublishSkipped(PublishSkipped { namespace_suffix, track_name }))
1760            }
1761        }
1762    }
1763
1764    pub fn message_type(&self) -> MessageType {
1765        match self {
1766            ControlMessage::Setup(_) => MessageType::Setup,
1767            ControlMessage::GoAway(_) => MessageType::GoAway,
1768            ControlMessage::RequestOk(_) => MessageType::RequestOk,
1769            ControlMessage::RequestError(_) => MessageType::RequestError,
1770            ControlMessage::Subscribe(_) => MessageType::Subscribe,
1771            ControlMessage::SubscribeOk(_) => MessageType::SubscribeOk,
1772            ControlMessage::RequestUpdate(_) => MessageType::RequestUpdate,
1773            ControlMessage::Publish(_) => MessageType::Publish,
1774            ControlMessage::PublishDone(_) => MessageType::PublishDone,
1775            ControlMessage::PublishNamespace(_) => MessageType::PublishNamespace,
1776            ControlMessage::Namespace(_) => MessageType::Namespace,
1777            ControlMessage::NamespaceDone(_) => MessageType::NamespaceDone,
1778            ControlMessage::SubscribeNamespace(_) => MessageType::SubscribeNamespace,
1779            ControlMessage::SubscribeTracks(_) => MessageType::SubscribeTracks,
1780            ControlMessage::TrackStatus(_) => MessageType::TrackStatus,
1781            ControlMessage::Fetch(_) => MessageType::Fetch,
1782            ControlMessage::FetchOk(_) => MessageType::FetchOk,
1783            ControlMessage::PublishSkipped(_) => MessageType::PublishSkipped,
1784        }
1785    }
1786}
1787
1788#[cfg(test)]
1789mod tests {
1790    use super::*;
1791
1792    /// Frame `body` as a draft-19 control message of `type_id`, declaring
1793    /// `declared_len` rather than the body's real length. Used to build the
1794    /// mismatched frame the length rule is about.
1795    fn frame_with_declared_len(type_id: u64, declared_len: u16, body: &[u8]) -> Vec<u8> {
1796        let mut out = Vec::new();
1797        VarInt::from_u64_moqt(type_id).encode_moqt::<Wire>(&mut out);
1798        out.put_u16(declared_len);
1799        out.put_slice(body);
1800        out
1801    }
1802
1803    fn frame(type_id: u64, body: &[u8]) -> Vec<u8> {
1804        frame_with_declared_len(type_id, body.len() as u16, body)
1805    }
1806
1807    /// A SUBSCRIBE body: request id 1, namespace ("a"), track name "b", and
1808    /// `params` already encoded.
1809    fn subscribe_body(params: &[u8]) -> Vec<u8> {
1810        let mut body = vec![0x01, 0x01, 0x01, b'a', 0x01, b'b'];
1811        body.extend_from_slice(params);
1812        body
1813    }
1814
1815    /// Draft-19 Section 10: "If the length does not match the length of the
1816    /// Message Body, the receiver MUST close the session with a
1817    /// PROTOCOL_VIOLATION."
1818    ///
1819    /// Without the trailing-byte check in `decode` this SUBSCRIBE parses and
1820    /// the two surplus bytes vanish:
1821    ///
1822    /// ```text
1823    /// assertion `left == right` failed
1824    ///   left: Ok(Subscribe(Subscribe { request_id: VarInt(1), track_namespace:
1825    ///         TrackNamespace([[97]]), track_name: [98], parameters: [] }))
1826    ///  right: Err(InvalidField)
1827    /// ```
1828    #[test]
1829    fn a_message_body_shorter_than_the_declared_length_is_refused() {
1830        let body = subscribe_body(&[0x00]);
1831        let mut junked = body.clone();
1832        junked.extend_from_slice(&[0xff, 0xff]);
1833        let bytes = frame_with_declared_len(0x03, (body.len() + 2) as u16, &junked);
1834
1835        let mut buf = &bytes[..];
1836        assert_eq!(
1837            ControlMessage::decode(&mut buf),
1838            Err(CodecError::ControlMessageLengthMismatch {
1839                declared: (body.len() + 2),
1840                detail: "its fields left bytes unread",
1841            })
1842        );
1843
1844        // The same body with an honest length still decodes, so the guard
1845        // rejects the mismatch and not the message.
1846        let honest = frame(0x03, &body);
1847        let mut buf = &honest[..];
1848        assert!(ControlMessage::decode(&mut buf).is_ok());
1849    }
1850
1851    /// Draft-19 Section 1.4.4: "The reason phrase length has a maximum value of
1852    /// 1024 bytes. If an endpoint receives a length exceeding the maximum, it
1853    /// MUST close the session with a PROTOCOL_VIOLATION".
1854    ///
1855    /// Without the decode-side bound the 2000-byte phrase is handed to the
1856    /// application:
1857    ///
1858    /// ```text
1859    /// assertion `left == right` failed
1860    ///   left: Ok(RequestError(RequestError { error_code: VarInt(1),
1861    ///         retry_interval: VarInt(0), reason_phrase: [120, 120, ...],
1862    ///         redirect: None }))
1863    ///  right: Err(ReasonPhraseTooLong)
1864    /// ```
1865    ///
1866    /// (The 2000 repeated bytes of the phrase are elided from that transcript.)
1867    #[test]
1868    fn an_over_long_reason_phrase_is_refused_on_decode() {
1869        for (type_id, prefix) in [(0x05u64, vec![0x01, 0x00]), (0x0B, vec![0x01, 0x00])] {
1870            let mut body = prefix;
1871            let over = MAX_REASON_PHRASE_LENGTH + 976;
1872            VarInt::from_usize(over).encode_moqt::<Wire>(&mut body);
1873            body.extend(std::iter::repeat_n(b'x', over));
1874            let bytes = frame(type_id, &body);
1875
1876            let mut buf = &bytes[..];
1877            assert_eq!(
1878                ControlMessage::decode(&mut buf),
1879                Err(CodecError::ReasonPhraseTooLong),
1880                "message type 0x{type_id:x}"
1881            );
1882        }
1883    }
1884
1885    /// Draft-19 Section 10.4: "The maximum length of the New Session URI is
1886    /// 8,192 bytes. If an endpoint receives a length exceeding the maximum, it
1887    /// MUST close the session with a PROTOCOL_VIOLATION."
1888    ///
1889    /// Without the decode-side bound the oversized URI reaches the application
1890    /// and a migrating endpoint follows it:
1891    ///
1892    /// ```text
1893    /// assertion `left == right` failed
1894    ///   left: Ok(GoAway(GoAway { new_session_uri: [117, 117, ...],
1895    ///         timeout: VarInt(0) }))
1896    ///  right: Err(GoAwayUriTooLong)
1897    /// ```
1898    ///
1899    /// (The 9000 repeated bytes of the URI are elided from that transcript.)
1900    #[test]
1901    fn an_over_long_goaway_uri_is_refused_on_decode() {
1902        let over = MAX_GOAWAY_URI_LENGTH + 808;
1903        let mut body = Vec::new();
1904        VarInt::from_usize(over).encode_moqt::<Wire>(&mut body);
1905        body.extend(std::iter::repeat_n(b'u', over));
1906        body.push(0x00); // timeout
1907        let bytes = frame(0x10, &body);
1908
1909        let mut buf = &bytes[..];
1910        assert_eq!(ControlMessage::decode(&mut buf), Err(CodecError::GoAwayUriTooLong));
1911    }
1912
1913    /// Draft-19 Section 10.2.8 (GROUP_ORDER): "The allowed values are Ascending
1914    /// (0x1) or Descending (0x2). If an endpoint receives a value outside this
1915    /// range, it MUST close the session with PROTOCOL_VIOLATION." Section
1916    /// 10.2.17 says the same of FORWARD with the values 0 and 1.
1917    ///
1918    /// Without `uint8_value_in_range` the out-of-range byte is handed up as an
1919    /// ordinary parameter:
1920    ///
1921    /// ```text
1922    /// assertion `left == right` failed
1923    ///   left: Ok(Subscribe(Subscribe { request_id: VarInt(1), track_namespace:
1924    ///         TrackNamespace([[97]]), track_name: [98], parameters:
1925    ///         [KeyValuePair { key: VarInt(34), value: Varint(VarInt(7)) }] }))
1926    ///  right: Err(InvalidField)
1927    /// ```
1928    #[test]
1929    fn a_uint8_parameter_outside_its_range_is_refused() {
1930        // key, rejected value, accepted value
1931        let cases = [(0x22u8, 7u8, 2u8), (0x10, 9, 1)];
1932        for (key, bad, good) in cases {
1933            let bytes = frame(0x03, &subscribe_body(&[0x01, key, bad]));
1934            let mut buf = &bytes[..];
1935            assert_eq!(
1936                ControlMessage::decode(&mut buf),
1937                Err(CodecError::ParameterValueOutOfRange { key: key as u64, value: bad as u64 }),
1938                "parameter 0x{key:x} value {bad}"
1939            );
1940
1941            let bytes = frame(0x03, &subscribe_body(&[0x01, key, good]));
1942            let mut buf = &bytes[..];
1943            assert!(
1944                ControlMessage::decode(&mut buf).is_ok(),
1945                "parameter 0x{key:x} value {good} should still decode"
1946            );
1947        }
1948    }
1949
1950    /// SUBSCRIBER_PRIORITY (0x20) is a uint8 with no restricted range, so it
1951    /// must keep accepting the whole 0-255 span. This is the negative half of
1952    /// the range check: a table that over-reached would fail here.
1953    #[test]
1954    fn subscriber_priority_still_accepts_the_whole_byte_range() {
1955        for value in [0u8, 1, 2, 128, 255] {
1956            let bytes = frame(0x03, &subscribe_body(&[0x01, 0x20, value]));
1957            let mut buf = &bytes[..];
1958            assert!(ControlMessage::decode(&mut buf).is_ok(), "priority {value}");
1959        }
1960    }
1961
1962    fn param(key: u64, value: &[u8]) -> KeyValuePair {
1963        KeyValuePair { key: VarInt::from_u64_moqt(key), value: KvpValue::Bytes(value.to_vec()) }
1964    }
1965
1966    /// Draft-19 Section 10.2.16: "The LARGEST_OBJECT parameter (Parameter Type
1967    /// 0x9) is a Location." Section 10.2 defines Location as "Two consecutive
1968    /// varints (Group, Object)" — the value carries no length of its own.
1969    ///
1970    /// The frame below is built from the draft rather than from this encoder:
1971    /// REQUEST_OK, four body bytes, one parameter, type delta `0x09`, then the
1972    /// two varints `0x0a` and `0x03` for Location (10, 3). A length-prefixed
1973    /// spelling would need a fifth byte.
1974    ///
1975    /// With `0x09` back in the Length-prefixed arm, the decoder reads the
1976    /// group varint `0x0a` as a value length of 10 and runs off the end of a
1977    /// four-byte body. Both this test and
1978    /// [`a_location_does_not_eat_the_block_that_follows_it`] fail with:
1979    ///
1980    /// ```text
1981    /// spec-correct frame must decode: UnexpectedEnd
1982    /// ```
1983    #[test]
1984    fn largest_object_is_two_bare_varints() {
1985        let body = [0x01, 0x09, 0x0a, 0x03];
1986        let bytes = frame(0x07, &body);
1987
1988        let msg = ControlMessage::decode(&mut &bytes[..]).expect("spec-correct frame must decode");
1989        let ControlMessage::RequestOk(ok) = &msg else {
1990            panic!("expected REQUEST_OK, got {msg:?}")
1991        };
1992        assert_eq!(ok.parameters, vec![param(0x09, &[0x0a, 0x03])]);
1993        assert!(ok.track_properties.is_empty(), "the four body bytes are all parameter");
1994
1995        let mut out = Vec::new();
1996        msg.encode(&mut out).expect("re-encode");
1997        assert_eq!(out, bytes, "the value must go back out as the two bare varints it came in as");
1998    }
1999
2000    /// A Location value the encoder was handed but the decoder could not read
2001    /// back is refused on the way out, not written.
2002    ///
2003    /// LARGEST_OBJECT carries no length of its own — that is the whole point
2004    /// of the encoding — so `encode_parameters` writes its bytes verbatim. A
2005    /// value built in memory rather than decoded is under no obligation to be
2006    /// two varints, and before this check the codec answered `Ok(())` and put
2007    /// a frame on the wire that `ControlMessage::decode` then refused. One
2008    /// varint short and one varint long are the two ways to get it wrong.
2009    ///
2010    /// # What it catches
2011    ///
2012    /// Dropping the `is_location_value` guard from this draft's encode arm,
2013    /// run:
2014    ///
2015    /// ```text
2016    /// panicked at crates\moqtap-codec\src\draft19\message.rs:1382:13:
2017    /// LARGEST_OBJECT of one varint must not encode: the decoder cannot read it back
2018    ///
2019    /// test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 108 filtered out
2020    /// ```
2021    ///
2022    /// The sibling draft kept its guard and kept passing, which is what shows
2023    /// the check is per-draft and not inherited from somewhere shared.
2024    #[test]
2025    fn a_location_value_that_is_not_two_varints_is_refused_on_encode() {
2026        for (label, value) in
2027            [("one varint", vec![0x0a]), ("three varints", vec![0x0a, 0x03, 0x05])]
2028        {
2029            let msg = ControlMessage::RequestOk(RequestOk {
2030                parameters: vec![param(0x09, &value)],
2031                track_properties: Vec::new(),
2032            });
2033            let mut out = Vec::new();
2034            assert!(
2035                msg.encode(&mut out).is_err(),
2036                "LARGEST_OBJECT of {label} must not encode: the decoder cannot read it back"
2037            );
2038        }
2039
2040        // The well-formed value still goes out, so the check refuses the
2041        // malformed case and not the encoding itself.
2042        let msg = ControlMessage::RequestOk(RequestOk {
2043            parameters: vec![param(0x09, &[0x0a, 0x03])],
2044            track_properties: Vec::new(),
2045        });
2046        let mut out = Vec::new();
2047        msg.encode(&mut out).expect("a Location of exactly two varints must still encode");
2048        ControlMessage::decode(&mut &out[..]).expect("and must decode back");
2049    }
2050
2051    /// The same Location read through a message that carries other fields
2052    /// after it, so a stray length byte cannot hide in a trailing block.
2053    ///
2054    /// SUBSCRIBE_OK is track alias `0x05`, then the parameters, then the track
2055    /// properties. With LARGEST_OBJECT (10, 3) and one property
2056    /// (OBJECT_DELIVERY_TIMEOUT, type `0x02`, 5000ms as the two-byte varint
2057    /// `0x93 0x88`), the body is `05 01 09 0a 03 02 93 88`.
2058    #[test]
2059    fn a_location_does_not_eat_the_block_that_follows_it() {
2060        let body = [0x05, 0x01, 0x09, 0x0a, 0x03, 0x02, 0x93, 0x88];
2061        let bytes = frame(0x04, &body);
2062
2063        let msg = ControlMessage::decode(&mut &bytes[..]).expect("spec-correct frame must decode");
2064        let ControlMessage::SubscribeOk(ok) = &msg else {
2065            panic!("expected SUBSCRIBE_OK, got {msg:?}")
2066        };
2067        assert_eq!(ok.parameters, vec![param(0x09, &[0x0a, 0x03])]);
2068        assert_eq!(
2069            ok.track_properties,
2070            vec![KeyValuePair {
2071                key: VarInt::from_u64_moqt(0x02),
2072                value: KvpValue::Varint(VarInt::from_u64_moqt(5000)),
2073            }]
2074        );
2075
2076        let mut out = Vec::new();
2077        msg.encode(&mut out).expect("re-encode");
2078        assert_eq!(out, bytes);
2079    }
2080
2081    /// Draft-19 Section 10.2.19: the TRACK_NAMESPACE_PREFIX parameter
2082    /// (Parameter Type 0x34) "uses the Track Namespace encoding described in
2083    /// Section 2.4.1" — a varint field count followed by that many
2084    /// length-prefixed fields, and nothing in front of it. That encoding is not
2085    /// one of the four Section 10.2 lists, so it cannot be assumed to be
2086    /// Length-prefixed by default.
2087    ///
2088    /// The frame below is built from Section 2.4.1: REQUEST_UPDATE for request
2089    /// `7`, one parameter, type delta `0x34`, then the namespace ("live",
2090    /// "sports") as `02 04 "live" 06 "sports"`. Sixteen body bytes; a
2091    /// length-prefixed spelling would need a seventeenth for the outer length.
2092    ///
2093    /// With `0x34` back in the Length-prefixed arm the field count `0x02` is
2094    /// read as an outer length of two bytes, leaving eleven bytes of namespace
2095    /// unread. Draft-19's Section 10 body-length check turns that into a
2096    /// refusal rather than a truncated value:
2097    ///
2098    /// ```text
2099    /// spec-correct frame must decode: InvalidField
2100    /// ```
2101    ///
2102    /// That check is not a safety net here. Where the surplus lands inside the
2103    /// declared body — as in
2104    /// [`an_empty_track_namespace_prefix_is_one_zero_byte`], whose namespace is
2105    /// one byte long — the misread is silent, and that test fails instead with:
2106    ///
2107    /// ```text
2108    /// assertion `left == right` failed
2109    ///   left: [KeyValuePair { key: VarInt(52), value: Bytes([]) }]
2110    ///  right: [KeyValuePair { key: VarInt(52), value: Bytes([0]) }]
2111    /// ```
2112    #[test]
2113    fn track_namespace_prefix_is_a_bare_track_namespace() {
2114        let namespace: Vec<u8> = [&[0x02, 0x04][..], b"live", &[0x06][..], b"sports"].concat();
2115        assert_eq!(namespace.len(), 13);
2116
2117        let body: Vec<u8> = [&[0x07, 0x01, 0x34][..], &namespace].concat();
2118        assert_eq!(body.len(), 16);
2119        let bytes = frame(0x02, &body);
2120
2121        let msg = ControlMessage::decode(&mut &bytes[..]).expect("spec-correct frame must decode");
2122        let ControlMessage::RequestUpdate(update) = &msg else {
2123            panic!("expected REQUEST_UPDATE, got {msg:?}")
2124        };
2125        assert_eq!(update.request_id.into_inner(), 7);
2126        assert_eq!(update.parameters, vec![param(0x34, &namespace)]);
2127
2128        let mut out = Vec::new();
2129        msg.encode(&mut out).expect("re-encode");
2130        assert_eq!(out, bytes, "no outer length may appear in front of the Track Namespace");
2131    }
2132
2133    /// An empty prefix is a legal Track Namespace: Section 2.4.1 puts one at
2134    /// "between 0 and 32 Track Namespace Fields". On the wire that is the
2135    /// single byte `0x00`, and it must not be confused with a length-prefixed
2136    /// value of zero bytes.
2137    #[test]
2138    fn an_empty_track_namespace_prefix_is_one_zero_byte() {
2139        let body = [0x07, 0x01, 0x34, 0x00];
2140        let bytes = frame(0x02, &body);
2141
2142        let msg = ControlMessage::decode(&mut &bytes[..]).expect("empty prefix must decode");
2143        let ControlMessage::RequestUpdate(update) = &msg else {
2144            panic!("expected REQUEST_UPDATE, got {msg:?}")
2145        };
2146        assert_eq!(update.parameters, vec![param(0x34, &[0x00])]);
2147
2148        let mut out = Vec::new();
2149        msg.encode(&mut out).expect("re-encode");
2150        assert_eq!(out, bytes);
2151    }
2152}