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