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