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 would leave the rule enforced against the frame and
553 // invisible to the session.
554 let encoding =
555 param_encoding(abs_key).ok_or(CodecError::UnknownMessageParameter(abs_key))?;
556
557 let value = decode_parameter_value(encoding, abs_key, buf)?;
558
559 params.push(KeyValuePair { key: VarInt::from_u64_moqt(abs_key), value });
560 }
561 check_authorization_tokens(¶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 Section 3.3.1 and Section 9.11 both say the
1352 /// Location filter "specifies an inclusive range of Locations".
1353 ///
1354 /// So both draft-19 conventions are gone: there is no plus one, and an
1355 /// Object of 0 now means object 0 rather than the whole group. The bytes
1356 /// are identical between the two drafts and the meaning is not, and nothing
1357 /// on the wire distinguishes them — an encoder ported forward with its
1358 /// arithmetic intact fetches one object too many, and one whose end lands
1359 /// on object 0 fetches a single object where the same bytes cover a whole
1360 /// group on draft-19.
1361 ///
1362 /// **Ambiguous, and the draft leaves it so.** When the request's filter
1363 /// omitted `EndObject`, so the
1364 /// filter meant "all objects in the End Group", Section 9.12 does not say
1365 /// what to report — and with the `0`-means-whole-group encoding gone there
1366 /// is no way left to spell it. The same applies to a relative start. The
1367 /// reading this codec's corpus takes, and the only sane one, is the Object
1368 /// ID of the last Object actually covered; the draft does not say so, and
1369 /// nothing here can enforce it, because resolving it needs the track rather
1370 /// than the frame.
1371 pub end_object: VarInt,
1372 pub parameters: Vec<KeyValuePair>,
1373 pub track_properties: Vec<KeyValuePair>,
1374}
1375
1376// ============================================================
1377// Publish State Notify
1378// ============================================================
1379
1380/// PUBLISH_STATE_NOTIFY (0x22), new in draft-20 (Section 9.10, Figure 14).
1381///
1382/// ```text
1383/// PUBLISH_STATE_NOTIFY Message {
1384/// Type (vi64) = 0x22,
1385/// Length (16),
1386/// Number of Parameters (vi64),
1387/// Parameters (..) ...
1388/// }
1389/// ```
1390///
1391/// **There is no Request ID field.** The message is identified by the
1392/// subscription's bidirectional request stream it arrives on, the way
1393/// SUBSCRIBE_OK, PUBLISH_DONE and FETCH_OK are.
1394///
1395/// The rules a codec cannot check, because they are about the session rather
1396/// than the frame, and which the caller therefore owns (Section 9.10):
1397///
1398/// * it is sent by the **publisher only**, on a **subscription's** stream.
1399/// Receiving one for any other request type, or from the subscriber, MUST
1400/// close the session with a PROTOCOL_VIOLATION;
1401/// * it is **unilateral** — the receiver does not answer with REQUEST_OK or
1402/// REQUEST_ERROR — and it is not counted against the `MAX_REQUEST_UPDATES`
1403/// Setup Option;
1404/// * it carries only the parameters whose values changed, and an absent
1405/// parameter is unchanged;
1406/// * a publisher MUST NOT use it to change a subscriber-controlled parameter
1407/// unless the subscriber asked for the change;
1408/// * the publisher **MUST** include `LARGEST_OBJECT` (0x09) if known, so the
1409/// subscriber can locate the point in the track where the change took
1410/// effect. Nothing here enforces that: a missing required parameter is not
1411/// something this decoder detects, and a message that omits it is still a
1412/// well-formed frame.
1413#[derive(Debug, Clone, PartialEq, Eq)]
1414pub struct PublishStateNotify {
1415 pub parameters: Vec<KeyValuePair>,
1416}
1417
1418// ============================================================
1419// Publish Skipped
1420// ============================================================
1421
1422/// PUBLISH_SKIPPED (0x0F). Drafts 17 and 18 name this codepoint
1423/// PUBLISH_BLOCKED; the wire layout — track namespace suffix then track name —
1424/// is the same on all four drafts that carry the message.
1425#[derive(Debug, Clone, PartialEq, Eq)]
1426pub struct PublishSkipped {
1427 pub namespace_suffix: TrackNamespace,
1428 pub track_name: Vec<u8>,
1429}
1430
1431// ============================================================
1432// Unified Message Enum
1433// ============================================================
1434
1435#[derive(Debug, Clone, PartialEq, Eq)]
1436pub enum ControlMessage {
1437 Setup(Setup),
1438 GoAway(GoAway),
1439 RequestOk(RequestOk),
1440 RequestError(RequestError),
1441 Subscribe(Subscribe),
1442 SubscribeOk(SubscribeOk),
1443 RequestUpdate(RequestUpdate),
1444 Publish(Publish),
1445 PublishStateNotify(PublishStateNotify),
1446 PublishDone(PublishDone),
1447 PublishNamespace(PublishNamespace),
1448 Namespace(Namespace),
1449 NamespaceDone(NamespaceDone),
1450 SubscribeNamespace(SubscribeNamespace),
1451 SubscribeTracks(SubscribeTracks),
1452 TrackStatus(TrackStatus),
1453 Fetch(Fetch),
1454 FetchOk(FetchOk),
1455 PublishSkipped(PublishSkipped),
1456}
1457
1458// Draft-21 has no range check on a control message, and the absence is the
1459// draft's rather than an omission here.
1460//
1461// Draft-19 carried one: its FETCH held a `Start Location` and an `End Location`
1462// inline, and draft-19 Section 10.12.3 said "End Location MUST specify the
1463// same or a larger Location than Start Location for Standalone and Absolute
1464// Joining
1465// Fetches". Draft-20 deleted both fields with the rest of the FETCH rewrite
1466// (Section 9.11), so there is no pair left in any message to compare.
1467//
1468// What replaced them cannot express the malformation either. A `LOCATION_FILTER`
1469// (Section 3.3.1) states its end as an unsigned `EndGroupDelta` added to
1470// `StartGroup`, so the end group can never precede the start group; and within
1471// one group draft-21 states no rule about an `EndObject` below `StartObject`.
1472// Section 3.3.1 says the opposite for subscriptions — "A Location Filter on a
1473// subscription is always valid, even if it specifies a range entirely before
1474// Largest Object" — so refusing one would close sessions over a sentence that
1475// is not there.
1476//
1477// The one backwards-range rule draft-21 does keep is in Section 9.12: "If End
1478// Location is smaller than the Start Location in the corresponding FETCH the
1479// receiver MUST close the session with a PROTOCOL_VIOLATION." It compares a
1480// FETCH_OK against the FETCH it answers, which is session state and not
1481// anything one frame holds, so it belongs to the caller.
1482
1483/// Refuse a message whose discriminator disagrees with the fields beside it.
1484///
1485/// One draft-21 message carries a field that says which of the following fields
1486/// are on the wire: REQUEST_ERROR's Error Code, whose REDIRECT value (0x34) is
1487/// what puts the Redirect structure on the wire. This codec holds the
1488/// alternative in an `Option`, so a value can say one thing in its
1489/// discriminator and another in its body, and the two sides of the codec
1490/// resolve that differently — the encoder writes whatever the body holds, and
1491/// the decoder reads whatever the discriminator announces.
1492///
1493/// A REQUEST_ERROR with code REDIRECT and no Redirect body encodes to a message
1494/// that ends where the decoder expects a Connect URI length, so the peer reads
1495/// the redirect out of whatever follows or runs off the end. The mirror case is
1496/// quieter and no better: a Redirect body under any other error code is written
1497/// out and then skipped by a decoder that was never told to look for it, so the
1498/// sender believes it redirected a peer that never saw a redirect.
1499///
1500/// Draft-19 had a second discriminator here, FETCH's `Fetch Type`, choosing
1501/// between a standalone body and a joining pair. Draft-20 deleted the field and
1502/// both bodies (Section 9.11), so FETCH has nothing left to disagree with
1503/// itself about.
1504///
1505/// Refusing at the encoder keeps the two readings from ever diverging on the
1506/// wire.
1507fn check_discriminators(message: &ControlMessage) -> Result<(), CodecError> {
1508 // One arm, where drafts 17 to 19 have two, so an `if let` rather than a
1509 // one-armed `match`; a second discriminator would make it a `match`.
1510 if let ControlMessage::RequestError(m) = message {
1511 let code_is_redirect = m.error_code.into_inner() == request_error_codes::REDIRECT;
1512 if code_is_redirect != m.redirect.is_some() {
1513 return Err(CodecError::InvalidField);
1514 }
1515 }
1516 Ok(())
1517}
1518
1519/// Whether draft-21 lets Message Parameter `key` appear in `message`.
1520///
1521/// Section 9.20.1: "Each Message Parameter definition indicates the message
1522/// types in which it can appear. If it appears in some other type of message,
1523/// the receiving endpoint MUST close the connection with a PROTOCOL_VIOLATION."
1524/// One arm per entry in the Message Parameters registry (Section 16.7),
1525/// carrying the message types that entry's own definition names.
1526///
1527/// Four things about this draft the arms below fold in:
1528///
1529/// * Six of the names are one wire type. Section 9.3: "This document uses the
1530/// shorthand PUBLISH_OK, REQUEST_UPDATE_OK, TRACK_STATUS_OK,
1531/// SUBSCRIBE_NAMESPACE_OK, SUBSCRIBE_TRACKS_OK and PUBLISH_NAMESPACE_OK to
1532/// refer to a REQUEST_OK sent in response to the corresponding request type".
1533/// Which one a given REQUEST_OK is depends on the request its Request ID
1534/// answers, which is session state and not in the frame, so each of those
1535/// names widens the same arm and a REQUEST_OK is held to their union.
1536/// * The five Range Filters state their scope in Section 3.3.2 — draft-19's
1537/// Section 5.1.3 — rather than in their own subsections: the Track Property
1538/// filter "MAY appear multiple times in a SUBSCRIBE_TRACKS message or
1539/// REQUEST_UPDATE for it", and "all other filter parameters MAY appear
1540/// multiple times in a FETCH, SUBSCRIBE, SUBSCRIBE_TRACKS, or REQUEST_UPDATE
1541/// (on a subscription, from the subscriber only) message". Draft-19 listed
1542/// PUBLISH_OK in that second sentence and draft-20 removed it.
1543/// * **SUBSCRIBE_TRACKS takes the parameters whose own definitions name it,
1544/// plus `LOCATION_FILTER` (0x21) and `FILL_PARAMETERS` (0x23), and the two
1545/// extra are a decision this codec makes.** Section 9.18.1: "Any Parameter
1546/// that can be specified on a Subscription (ie: in SUBSCRIBE) is valid in
1547/// SUBSCRIBE_TRACKS, unless otherwise specified." The definitions are that
1548/// "otherwise": `AUTHORIZATION_TOKEN`, `FORWARD`, `GROUP_ORDER`,
1549/// `INCLUDE_PROPERTIES` and the Range Filters name SUBSCRIBE_TRACKS, and
1550/// `OBJECT_DELIVERY_TIMEOUT` (0x02), `RENDEZVOUS_TIMEOUT` (0x04),
1551/// `SUBGROUP_DELIVERY_TIMEOUT` (0x06), `SUBSCRIBER_PRIORITY` (0x20) and
1552/// `NEW_GROUP_REQUEST` (0x32) leave it out, so those five are refused there
1553/// under Section 9.20.1. The same section then closes: "To join Tracks
1554/// initiated via the resulting PUBLISHes, the subscriber can specify a
1555/// Location Filter and optionally include FILL_PARAMETERS" — two parameters
1556/// whose definitions also leave this message out. On those two alone the
1557/// section and the definitions with Section 9.20.1's rule conflict, from
1558/// draft-20 on. This codec follows the section for them: it is the one
1559/// sentence telling a subscriber what to put in this message, and a scope
1560/// miss ends the session.
1561/// * **`PUBLISH_OK` is gone from six definitions.** `OBJECT_DELIVERY_TIMEOUT`
1562/// (0x02), `SUBGROUP_DELIVERY_TIMEOUT` (0x06), `FORWARD` (0x10),
1563/// `SUBSCRIBER_PRIORITY` (0x20), `LOCATION_FILTER` (0x21) and
1564/// `NEW_GROUP_REQUEST` (0x32) each dropped it, and several gained `PUBLISH`
1565/// instead; the Range Filters dropped it via Section 3.3.2. `EXPIRES` (0x08)
1566/// is the only parameter that still names it. A subscriber that wants to
1567/// change something now sends a REQUEST_UPDATE after the PUBLISH_OK, and
1568/// sending any of the six on a PUBLISH_OK is a Section 9.20.1 violation.
1569/// Because PUBLISH_OK and REQUEST_OK are one wire type, this table cannot
1570/// see the difference: `M::RequestOk` is admitted wherever any of the six OK
1571/// names is, so the practical effect here is that the six lose their
1572/// `M::RequestOk` arm entirely.
1573///
1574/// FETCH_OK has no arm in the table below, and that is the draft's doing rather
1575/// than an omission here: Section 9.12 gives it a Parameters field and no
1576/// parameter definition names it, so every type this draft defines is "some
1577/// other type of message" there.
1578///
1579/// PUBLISH_STATE_NOTIFY is admitted by exactly three parameters, and treating
1580/// that list as closed is **a decision this codec makes**. Section 9.10 says
1581/// only "The semantics of each parameter, including whether it may appear in
1582/// PUBLISH_STATE_NOTIFY, are defined by the parameter", and `LARGEST_OBJECT`
1583/// (0x09), `FORWARD` (0x10) and `LOCATION_FILTER` (0x21) are the three whose
1584/// definitions name it. The draft implies the closure and never states it, so
1585/// anything else there is refused under Section 9.20.1 on that reading.
1586///
1587/// The table decides scope only. A type this draft does not define has no scope
1588/// to be outside of and is answered by [`CodecError::UnknownMessageParameter`],
1589/// which is why the final arm carries rather than refuses.
1590pub fn parameter_in_scope(key: u64, message: MessageType) -> bool {
1591 use MessageType as M;
1592 // SUBSCRIBE_TRACKS appears only in the arms whose definitions name it, and
1593 // in 0x21 and 0x23, which Section 9.18.1 names; see the doc comment above.
1594 match key {
1595 // Section 9.20.5 OBJECT_DELIVERY_TIMEOUT: "It MAY appear in a
1596 // SUBSCRIBE, PUBLISH, or REQUEST_UPDATE message." Draft-19 said
1597 // PUBLISH_OK where this says PUBLISH.
1598 0x02 => matches!(message, M::Subscribe | M::Publish | M::RequestUpdate),
1599 // Section 9.20.3 AUTHORIZATION TOKEN: "It MAY appear in a PUBLISH,
1600 // SUBSCRIBE, REQUEST_UPDATE, SUBSCRIBE_NAMESPACE, SUBSCRIBE_TRACKS,
1601 // PUBLISH_NAMESPACE, TRACK_STATUS or FETCH message." Unchanged from
1602 // draft-19; what draft-20 added is the sentence after it, "This
1603 // Parameter MUST NOT be copied from a SUBSCRIBE_TRACKS to the resulting
1604 // PUBLISH message Parameters", which is a rule about two messages and
1605 // so belongs to the caller rather than to this table.
1606 0x03 => matches!(
1607 message,
1608 M::Publish
1609 | M::Subscribe
1610 | M::RequestUpdate
1611 | M::SubscribeNamespace
1612 | M::SubscribeTracks
1613 | M::PublishNamespace
1614 | M::TrackStatus
1615 | M::Fetch
1616 ),
1617 // Section 9.20.7 RENDEZVOUS TIMEOUT: it "MAY appear in a SUBSCRIBE
1618 // message".
1619 0x04 => matches!(message, M::Subscribe),
1620 // Section 9.20.4 SUBGROUP_DELIVERY_TIMEOUT: "It MAY appear in a
1621 // SUBSCRIBE, PUBLISH, or REQUEST_UPDATE message." Draft-19 said
1622 // PUBLISH_OK where this says PUBLISH.
1623 0x06 => matches!(message, M::Subscribe | M::Publish | M::RequestUpdate),
1624 // Section 9.20.17 EXPIRES: "It MAY appear in SUBSCRIBE_OK, PUBLISH,
1625 // PUBLISH_OK, SUBSCRIBE_NAMESPACE_OK, SUBSCRIBE_TRACKS_OK,
1626 // PUBLISH_NAMESPACE_OK, or REQUEST_UPDATE_OK." Five of those seven are
1627 // a REQUEST_OK. The one parameter that still names PUBLISH_OK.
1628 0x08 => matches!(message, M::SubscribeOk | M::Publish | M::RequestOk),
1629 // Section 9.20.18 LARGEST OBJECT: "It MAY appear in SUBSCRIBE_OK,
1630 // PUBLISH, REQUEST_UPDATE_OK, TRACK_STATUS_OK, or
1631 // PUBLISH_STATE_NOTIFY." The last is new in draft-20.
1632 0x09 => {
1633 matches!(message, M::SubscribeOk | M::Publish | M::RequestOk | M::PublishStateNotify)
1634 }
1635 // Section 9.20.6 FILL TIMEOUT: it "MAY appear in a FETCH message, or
1636 // inside a FILL_PARAMETERS parameter (see Section 9.20.16) in a
1637 // SUBSCRIBE or REQUEST_UPDATE (for a subscription), where it applies to
1638 // the fill fetch stream." The nested half is not a scope this table can
1639 // answer — a nested parameter is not in the enclosing message at all,
1640 // per Section 9.20.16 — so it is enforced by
1641 // [`decode_fill_parameters`] against Table 6 instead.
1642 0x0A => matches!(message, M::Fetch),
1643 // Section 9.20.19 FORWARD: "It MAY appear in SUBSCRIBE, REQUEST_UPDATE
1644 // (for a subscription or a SUBSCRIBE_TRACKS request), PUBLISH,
1645 // SUBSCRIBE_TRACKS and PUBLISH_STATE_NOTIFY." Draft-19 listed
1646 // PUBLISH_OK and had no PUBLISH_STATE_NOTIFY.
1647 0x10 => matches!(
1648 message,
1649 M::Subscribe
1650 | M::RequestUpdate
1651 | M::Publish
1652 | M::SubscribeTracks
1653 | M::PublishStateNotify
1654 ),
1655 // Section 9.20.8 SUBSCRIBER PRIORITY: "It MAY appear in a SUBSCRIBE,
1656 // PUBLISH, FETCH, or REQUEST_UPDATE (for a subscription or FETCH)."
1657 // Draft-19 said PUBLISH_OK where this says PUBLISH.
1658 0x20 => matches!(message, M::Subscribe | M::Publish | M::Fetch | M::RequestUpdate),
1659 // Section 9.20.10 LOCATION FILTER: "The LOCATION_FILTER parameter
1660 // (Parameter Type 0x21) MAY appear in a FETCH, SUBSCRIBE, PUBLISH,
1661 // REQUEST_UPDATE (for a subscription) or PUBLISH_STATE_NOTIFY
1662 // message." Draft-20 opened the same definition with a sentence of its
1663 // own, "uses length-prefixed encoding", which draft-21 dropped; the
1664 // `Length` field is still in the structure and the wire is unchanged.
1665 // Draft-19 admitted SUBSCRIBE, PUBLISH_OK and REQUEST_UPDATE
1666 // and no FETCH, because a draft-19 FETCH carried its range in the
1667 // message instead. SUBSCRIBE_TRACKS is admitted on top of that list by
1668 // Section 9.18.1's closing sentence, as for FILL_PARAMETERS below.
1669 0x21 => matches!(
1670 message,
1671 M::Fetch
1672 | M::Subscribe
1673 | M::Publish
1674 | M::RequestUpdate
1675 | M::PublishStateNotify
1676 | M::SubscribeTracks
1677 ),
1678 // Section 9.20.9 GROUP ORDER: "It MAY appear in a SUBSCRIBE, PUBLISH,
1679 // SUBSCRIBE_TRACKS, or FETCH, or inside a FILL_PARAMETERS parameter".
1680 // The nested half is Table 6's, as for FILL_TIMEOUT above.
1681 0x22 => matches!(message, M::Subscribe | M::Publish | M::SubscribeTracks | M::Fetch),
1682 // Section 9.20.16 FILL_PARAMETERS, new in draft-20: it "MAY appear in a
1683 // SUBSCRIBE or REQUEST_UPDATE (for a subscription) message."
1684 //
1685 // SUBSCRIBE_TRACKS is admitted on top of those two by Section 9.18.1's
1686 // closing sentence, which tells the subscriber it "can specify a
1687 // Location Filter and optionally include FILL_PARAMETERS" there; see
1688 // the doc comment above for why that sentence wins over this list.
1689 //
1690 // FETCH is refused, and that is the case the corpus pins: a fill fetch
1691 // stream is something a subscription opens, and a FETCH already is one.
1692 0x23 => matches!(message, M::Subscribe | M::RequestUpdate | M::SubscribeTracks),
1693 // Section 3.3.2: "All other filter parameters MAY appear multiple times
1694 // in a FETCH, SUBSCRIBE, SUBSCRIBE_TRACKS, or REQUEST_UPDATE (on a
1695 // subscription, from the subscriber only) message." SUBGROUP_FILTER
1696 // (Section 9.20.11), OBJECTID_FILTER (9.20.12), PRIORITY_FILTER
1697 // (9.20.13) and OBJECT_PROPERTY_FILTER (9.20.14) are those four.
1698 // Draft-19's sentence also listed PUBLISH_OK.
1699 0x25..=0x28 => {
1700 matches!(message, M::Fetch | M::Subscribe | M::SubscribeTracks | M::RequestUpdate)
1701 }
1702 // Section 3.3.2, of TRACK_PROPERTY_FILTER (Section 9.20.15) alone: it
1703 // "MAY appear multiple times in a SUBSCRIBE_TRACKS message or
1704 // REQUEST_UPDATE for it". It selects tracks rather than objects, which
1705 // is why it is the one filter a SUBSCRIBE may not carry and the one
1706 // Table 6 keeps out of FILL_PARAMETERS.
1707 0x29 => matches!(message, M::SubscribeTracks | M::RequestUpdate),
1708 // Section 9.20.20 NEW GROUP REQUEST: "It MAY appear in SUBSCRIBE or
1709 // REQUEST_UPDATE for a subscription." Draft-19 also listed PUBLISH_OK.
1710 0x32 => matches!(message, M::Subscribe | M::RequestUpdate),
1711 // Section 9.20.21 TRACK_NAMESPACE_PREFIX: "It MAY appear in
1712 // REQUEST_UPDATE for a SUBSCRIBE_NAMESPACE or SUBSCRIBE_TRACKS
1713 // request." The two named there are the request being updated, not two
1714 // more places the parameter may be written.
1715 0x34 => matches!(message, M::RequestUpdate),
1716 // Section 9.20.22 INCLUDE_PROPERTIES, new in draft-20: "It MAY appear
1717 // in SUBSCRIBE, TRACK_STATUS, FETCH or SUBSCRIBE_TRACKS."
1718 0x35 => matches!(message, M::Subscribe | M::TrackStatus | M::Fetch | M::SubscribeTracks),
1719 _ => true,
1720 }
1721}
1722
1723/// Refuse a message carrying a Message Parameter its own definition does not
1724/// place there.
1725///
1726/// Section 9.20.1 answers this with a close, which the drafts below do not.
1727/// Draft-16 Section 9.2.2, and drafts 07 through 15 under the older name
1728/// Version Specific Parameters, end the same sentence "it MUST be ignored" —
1729/// so this check belongs to drafts 17 and later and to no draft before them.
1730///
1731/// Applied on both sides. A parameter outside its scope is one the peer must
1732/// close the session over, so writing one is a way to end a session rather than
1733/// a way to ask for anything.
1734fn check_parameter_scope(message: &ControlMessage) -> Result<(), CodecError> {
1735 let parameters = match message {
1736 ControlMessage::RequestOk(m) => &m.parameters,
1737 ControlMessage::Subscribe(m) => &m.parameters,
1738 ControlMessage::SubscribeOk(m) => &m.parameters,
1739 ControlMessage::RequestUpdate(m) => &m.parameters,
1740 ControlMessage::Publish(m) => &m.parameters,
1741 ControlMessage::PublishStateNotify(m) => &m.parameters,
1742 ControlMessage::PublishNamespace(m) => &m.parameters,
1743 ControlMessage::SubscribeNamespace(m) => &m.parameters,
1744 ControlMessage::SubscribeTracks(m) => &m.parameters,
1745 ControlMessage::TrackStatus(m) => &m.parameters,
1746 ControlMessage::Fetch(m) => &m.parameters,
1747 ControlMessage::FetchOk(m) => &m.parameters,
1748 // No Message Parameters field. SETUP is named here rather than left to
1749 // a wildcard because the draft says why it can never have one: Section
1750 // 9.20.1 notes that "since Setup Options use a separate namespace, it
1751 // is impossible for Message Parameters to appear in Setup messages",
1752 // and this codec keeps the two namespaces in separate fields.
1753 ControlMessage::Setup(_)
1754 | ControlMessage::GoAway(_)
1755 | ControlMessage::RequestError(_)
1756 | ControlMessage::PublishDone(_)
1757 | ControlMessage::Namespace(_)
1758 | ControlMessage::NamespaceDone(_)
1759 | ControlMessage::PublishSkipped(_) => return Ok(()),
1760 };
1761
1762 let message_type = message.message_type();
1763 for parameter in parameters {
1764 let key = parameter.key.into_inner();
1765 if !parameter_in_scope(key, message_type) {
1766 return Err(CodecError::ParameterOutOfScope { key, message_type: message_type.id() });
1767 }
1768 }
1769 Ok(())
1770}
1771
1772impl ControlMessage {
1773 pub fn encode(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
1774 check_discriminators(self)?;
1775 check_parameter_scope(self)?;
1776 let mut body = Vec::with_capacity(256);
1777 self.encode_body(&mut body)?;
1778
1779 if body.len() > MAX_MESSAGE_LENGTH {
1780 return Err(CodecError::MessageTooLong(body.len()));
1781 }
1782
1783 let msg_type = self.message_type();
1784 VarInt::from_usize(msg_type.id() as usize).encode_moqt::<Wire>(buf);
1785 // Draft-21: 16-bit length (big-endian)
1786 buf.put_u16(body.len() as u16);
1787 buf.put_slice(&body);
1788 Ok(())
1789 }
1790
1791 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
1792 let type_id = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
1793 let msg_type =
1794 MessageType::from_id(type_id).ok_or(CodecError::UnknownMessageType(type_id))?;
1795 // Draft-21: 16-bit length (big-endian)
1796 if buf.remaining() < 2 {
1797 return Err(CodecError::UnexpectedEnd);
1798 }
1799 let body_len = buf.get_u16() as usize;
1800 if buf.remaining() < body_len {
1801 return Err(CodecError::UnexpectedEnd);
1802 }
1803 let body_bytes = buf.copy_to_bytes(body_len);
1804 let mut body = &body_bytes[..];
1805 let msg = match Self::decode_body(msg_type, &mut body) {
1806 Ok(msg) => msg,
1807 // The fields wanted more bytes than the Length allowed. This buffer
1808 // is already bounded by that Length, so running out inside it cannot
1809 // mean the message is still arriving - which is what the same error
1810 // means everywhere else, and why a reader loops on it rather than
1811 // closing. Here there is nothing left to arrive.
1812 Err(
1813 CodecError::UnexpectedEnd
1814 | CodecError::Kvp(crate::kvp::KvpError::UnexpectedEnd)
1815 | CodecError::Kvp(crate::kvp::KvpError::VarInt(
1816 crate::varint::VarIntError::UnexpectedEnd,
1817 ))
1818 | CodecError::VarInt(crate::varint::VarIntError::UnexpectedEnd),
1819 ) => {
1820 return Err(CodecError::ControlMessageLengthMismatch {
1821 declared: body_len,
1822 detail: "its fields ran past the end",
1823 });
1824 }
1825 Err(e) => return Err(e),
1826 };
1827 check_parameter_scope(&msg)?;
1828 // Draft-21 Section 9: "If the length does not match the length of the
1829 // Message Body, the receiver MUST close the session with a
1830 // PROTOCOL_VIOLATION." A body parser that stops short leaves bytes
1831 // here; without this the surplus is discarded and a truncated or
1832 // mis-framed field looks like a well-formed message.
1833 if body.has_remaining() {
1834 return Err(CodecError::ControlMessageLengthMismatch {
1835 declared: body_len,
1836 detail: "its fields left bytes unread",
1837 });
1838 }
1839 Ok(msg)
1840 }
1841
1842 fn encode_body(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
1843 match self {
1844 ControlMessage::Setup(m) => {
1845 encode_setup_options(&m.options, buf)?;
1846 }
1847 ControlMessage::GoAway(m) => {
1848 if m.new_session_uri.len() > MAX_GOAWAY_URI_LENGTH {
1849 return Err(CodecError::GoAwayUriTooLong);
1850 }
1851 VarInt::from_usize(m.new_session_uri.len()).encode_moqt::<Wire>(buf);
1852 buf.put_slice(&m.new_session_uri);
1853 m.timeout.encode_moqt::<Wire>(buf);
1854 }
1855 ControlMessage::RequestOk(m) => {
1856 encode_parameters(&m.parameters, buf)?;
1857 encode_track_properties(&m.track_properties, buf)?;
1858 }
1859 ControlMessage::RequestError(m) => {
1860 if m.reason_phrase.len() > MAX_REASON_PHRASE_LENGTH {
1861 return Err(CodecError::ReasonPhraseTooLong);
1862 }
1863 m.error_code.encode_moqt::<Wire>(buf);
1864 m.retry_interval.encode_moqt::<Wire>(buf);
1865 VarInt::from_usize(m.reason_phrase.len()).encode_moqt::<Wire>(buf);
1866 buf.put_slice(&m.reason_phrase);
1867 if let Some(r) = &m.redirect {
1868 r.track_namespace.validate_moqt()?;
1869 check_full_track_name(&r.track_namespace, &r.track_name)?;
1870 VarInt::from_usize(r.connect_uri.len()).encode_moqt::<Wire>(buf);
1871 buf.put_slice(&r.connect_uri);
1872 r.track_namespace.encode_moqt::<Wire>(buf);
1873 VarInt::from_usize(r.track_name.len()).encode_moqt::<Wire>(buf);
1874 buf.put_slice(&r.track_name);
1875 }
1876 }
1877 ControlMessage::Subscribe(m) => {
1878 m.track_namespace.validate_moqt()?;
1879 check_full_track_name(&m.track_namespace, &m.track_name)?;
1880 m.request_id.encode_moqt::<Wire>(buf);
1881 m.track_namespace.encode_moqt::<Wire>(buf);
1882 VarInt::from_usize(m.track_name.len()).encode_moqt::<Wire>(buf);
1883 buf.put_slice(&m.track_name);
1884 encode_parameters(&m.parameters, buf)?;
1885 }
1886 ControlMessage::SubscribeOk(m) => {
1887 m.track_alias.encode_moqt::<Wire>(buf);
1888 encode_parameters(&m.parameters, buf)?;
1889 encode_track_properties(&m.track_properties, buf)?;
1890 }
1891 ControlMessage::RequestUpdate(m) => {
1892 m.request_id.encode_moqt::<Wire>(buf);
1893 encode_parameters(&m.parameters, buf)?;
1894 }
1895 ControlMessage::Publish(m) => {
1896 m.track_namespace.validate_moqt()?;
1897 check_full_track_name(&m.track_namespace, &m.track_name)?;
1898 m.request_id.encode_moqt::<Wire>(buf);
1899 m.track_namespace.encode_moqt::<Wire>(buf);
1900 VarInt::from_usize(m.track_name.len()).encode_moqt::<Wire>(buf);
1901 buf.put_slice(&m.track_name);
1902 m.track_alias.encode_moqt::<Wire>(buf);
1903 encode_parameters(&m.parameters, buf)?;
1904 encode_track_properties(&m.track_properties, buf)?;
1905 }
1906 // Section 9.10, Figure 14: parameter count, then parameters,
1907 // and nothing else. No Request ID — the subscription's stream is
1908 // what identifies the message.
1909 ControlMessage::PublishStateNotify(m) => {
1910 encode_parameters(&m.parameters, buf)?;
1911 }
1912 ControlMessage::PublishDone(m) => {
1913 if m.reason_phrase.len() > MAX_REASON_PHRASE_LENGTH {
1914 return Err(CodecError::ReasonPhraseTooLong);
1915 }
1916 m.status_code.encode_moqt::<Wire>(buf);
1917 m.stream_count.encode_moqt::<Wire>(buf);
1918 VarInt::from_usize(m.reason_phrase.len()).encode_moqt::<Wire>(buf);
1919 buf.put_slice(&m.reason_phrase);
1920 }
1921 ControlMessage::PublishNamespace(m) => {
1922 m.track_namespace.validate_moqt()?;
1923 m.request_id.encode_moqt::<Wire>(buf);
1924 m.track_namespace.encode_moqt::<Wire>(buf);
1925 encode_parameters(&m.parameters, buf)?;
1926 }
1927 ControlMessage::Namespace(m) => {
1928 m.namespace_suffix.validate_moqt()?;
1929 m.namespace_suffix.encode_moqt::<Wire>(buf);
1930 }
1931 ControlMessage::NamespaceDone(m) => {
1932 m.namespace_suffix.validate_moqt()?;
1933 m.namespace_suffix.encode_moqt::<Wire>(buf);
1934 }
1935 ControlMessage::SubscribeNamespace(m) => {
1936 m.namespace_prefix.validate_moqt()?;
1937 m.request_id.encode_moqt::<Wire>(buf);
1938 m.namespace_prefix.encode_moqt::<Wire>(buf);
1939 encode_parameters(&m.parameters, buf)?;
1940 }
1941 ControlMessage::SubscribeTracks(m) => {
1942 m.namespace_prefix.validate_moqt()?;
1943 m.request_id.encode_moqt::<Wire>(buf);
1944 m.namespace_prefix.encode_moqt::<Wire>(buf);
1945 encode_parameters(&m.parameters, buf)?;
1946 }
1947 ControlMessage::TrackStatus(m) => {
1948 m.track_namespace.validate_moqt()?;
1949 check_full_track_name(&m.track_namespace, &m.track_name)?;
1950 m.request_id.encode_moqt::<Wire>(buf);
1951 m.track_namespace.encode_moqt::<Wire>(buf);
1952 VarInt::from_usize(m.track_name.len()).encode_moqt::<Wire>(buf);
1953 buf.put_slice(&m.track_name);
1954 encode_parameters(&m.parameters, buf)?;
1955 }
1956 // Section 9.11, Figure 15. Byte-identical to SUBSCRIBE above:
1957 // no Fetch Type, no inline range, and the namespace and name where
1958 // draft-19 put them inside its Standalone Fetch.
1959 ControlMessage::Fetch(m) => {
1960 m.track_namespace.validate_moqt()?;
1961 check_full_track_name(&m.track_namespace, &m.track_name)?;
1962 m.request_id.encode_moqt::<Wire>(buf);
1963 m.track_namespace.encode_moqt::<Wire>(buf);
1964 VarInt::from_usize(m.track_name.len()).encode_moqt::<Wire>(buf);
1965 buf.put_slice(&m.track_name);
1966 encode_parameters(&m.parameters, buf)?;
1967 }
1968 ControlMessage::FetchOk(m) => {
1969 buf.put_u8(m.end_of_track);
1970 m.end_group.encode_moqt::<Wire>(buf);
1971 m.end_object.encode_moqt::<Wire>(buf);
1972 encode_parameters(&m.parameters, buf)?;
1973 encode_track_properties(&m.track_properties, buf)?;
1974 }
1975 ControlMessage::PublishSkipped(m) => {
1976 m.namespace_suffix.validate_moqt()?;
1977 check_full_track_name(&m.namespace_suffix, &m.track_name)?;
1978 m.namespace_suffix.encode_moqt::<Wire>(buf);
1979 VarInt::from_usize(m.track_name.len()).encode_moqt::<Wire>(buf);
1980 buf.put_slice(&m.track_name);
1981 }
1982 }
1983 Ok(())
1984 }
1985
1986 fn decode_body(msg_type: MessageType, buf: &mut impl Buf) -> Result<Self, CodecError> {
1987 match msg_type {
1988 MessageType::Setup => {
1989 let options = decode_setup_options(buf)?;
1990 Ok(ControlMessage::Setup(Setup { options }))
1991 }
1992 MessageType::GoAway => {
1993 let uri_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
1994 // Draft-21 Section 9.2: an endpoint that receives a New
1995 // Session URI Length above the maximum MUST close the session
1996 // with a PROTOCOL_VIOLATION. Checked here as well as on encode
1997 // so an oversized URI never reaches the application.
1998 if uri_len > MAX_GOAWAY_URI_LENGTH {
1999 return Err(CodecError::GoAwayUriTooLong);
2000 }
2001 let uri = read_bytes(buf, uri_len)?;
2002 let timeout = VarInt::decode_moqt::<Wire>(buf)?;
2003 Ok(ControlMessage::GoAway(GoAway { new_session_uri: uri, timeout }))
2004 }
2005 MessageType::RequestOk => {
2006 let parameters = decode_parameters(buf)?;
2007 let track_properties = decode_track_properties(buf)?;
2008 Ok(ControlMessage::RequestOk(RequestOk { parameters, track_properties }))
2009 }
2010 MessageType::RequestError => {
2011 let error_code = VarInt::decode_moqt::<Wire>(buf)?;
2012 let retry_interval = VarInt::decode_moqt::<Wire>(buf)?;
2013 let reason_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
2014 // Draft-21 Section 8.5: a received reason phrase length above
2015 // the maximum MUST close the session with a PROTOCOL_VIOLATION.
2016 if reason_len > MAX_REASON_PHRASE_LENGTH {
2017 return Err(CodecError::ReasonPhraseTooLong);
2018 }
2019 let reason_phrase = read_bytes(buf, reason_len)?;
2020 let redirect = if error_code.into_inner() == request_error_codes::REDIRECT {
2021 let uri_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
2022 let connect_uri = read_bytes(buf, uri_len)?;
2023 let track_namespace = TrackNamespace::decode_allow_empty_moqt::<Wire>(buf)?;
2024 let name_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
2025 let track_name = read_bytes(buf, name_len)?;
2026 check_full_track_name(&track_namespace, &track_name)?;
2027 Some(Redirect { connect_uri, track_namespace, track_name })
2028 } else {
2029 None
2030 };
2031 Ok(ControlMessage::RequestError(RequestError {
2032 error_code,
2033 retry_interval,
2034 reason_phrase,
2035 redirect,
2036 }))
2037 }
2038 MessageType::Subscribe => {
2039 let request_id = VarInt::decode_moqt::<Wire>(buf)?;
2040 let track_namespace = TrackNamespace::decode_moqt::<Wire>(buf)?;
2041 let tn_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
2042 let track_name = read_bytes(buf, tn_len)?;
2043 check_full_track_name(&track_namespace, &track_name)?;
2044 let parameters = decode_parameters(buf)?;
2045 Ok(ControlMessage::Subscribe(Subscribe {
2046 request_id,
2047 track_namespace,
2048 track_name,
2049 parameters,
2050 }))
2051 }
2052 MessageType::SubscribeOk => {
2053 let track_alias = VarInt::decode_moqt::<Wire>(buf)?;
2054 let parameters = decode_parameters(buf)?;
2055 let track_properties = decode_track_properties(buf)?;
2056 Ok(ControlMessage::SubscribeOk(SubscribeOk {
2057 track_alias,
2058 parameters,
2059 track_properties,
2060 }))
2061 }
2062 MessageType::RequestUpdate => {
2063 let request_id = VarInt::decode_moqt::<Wire>(buf)?;
2064 let parameters = decode_parameters(buf)?;
2065 Ok(ControlMessage::RequestUpdate(RequestUpdate { request_id, parameters }))
2066 }
2067 MessageType::Publish => {
2068 let request_id = VarInt::decode_moqt::<Wire>(buf)?;
2069 let track_namespace = TrackNamespace::decode_moqt::<Wire>(buf)?;
2070 let tn_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
2071 let track_name = read_bytes(buf, tn_len)?;
2072 let track_alias = VarInt::decode_moqt::<Wire>(buf)?;
2073 check_full_track_name(&track_namespace, &track_name)?;
2074 let parameters = decode_parameters(buf)?;
2075 let track_properties = decode_track_properties(buf)?;
2076 Ok(ControlMessage::Publish(Publish {
2077 request_id,
2078 track_namespace,
2079 track_name,
2080 track_alias,
2081 parameters,
2082 track_properties,
2083 }))
2084 }
2085 MessageType::PublishStateNotify => {
2086 let parameters = decode_parameters(buf)?;
2087 Ok(ControlMessage::PublishStateNotify(PublishStateNotify { parameters }))
2088 }
2089 MessageType::PublishDone => {
2090 // The Status Code is carried up whatever it is. Section 13
2091 // forbids closing the session over an unknown one and requires
2092 // it to be read as INTERNAL_ERROR, so refusing the frame here
2093 // would take a choice away from the caller that the draft gives
2094 // it. See [`PublishDone::status_code`].
2095 let status_code = VarInt::decode_moqt::<Wire>(buf)?;
2096 let stream_count = VarInt::decode_moqt::<Wire>(buf)?;
2097 let reason_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
2098 // Draft-21 Section 8.5, same bound as REQUEST_ERROR above.
2099 if reason_len > MAX_REASON_PHRASE_LENGTH {
2100 return Err(CodecError::ReasonPhraseTooLong);
2101 }
2102 let reason_phrase = read_bytes(buf, reason_len)?;
2103 Ok(ControlMessage::PublishDone(PublishDone {
2104 status_code,
2105 stream_count,
2106 reason_phrase,
2107 }))
2108 }
2109 MessageType::PublishNamespace => {
2110 let request_id = VarInt::decode_moqt::<Wire>(buf)?;
2111 let track_namespace = TrackNamespace::decode_moqt::<Wire>(buf)?;
2112 let parameters = decode_parameters(buf)?;
2113 Ok(ControlMessage::PublishNamespace(PublishNamespace {
2114 request_id,
2115 track_namespace,
2116 parameters,
2117 }))
2118 }
2119 MessageType::Namespace => {
2120 let namespace_suffix = TrackNamespace::decode_allow_empty_moqt::<Wire>(buf)?;
2121 Ok(ControlMessage::Namespace(Namespace { namespace_suffix }))
2122 }
2123 MessageType::NamespaceDone => {
2124 let namespace_suffix = TrackNamespace::decode_allow_empty_moqt::<Wire>(buf)?;
2125 Ok(ControlMessage::NamespaceDone(NamespaceDone { namespace_suffix }))
2126 }
2127 MessageType::SubscribeNamespace => {
2128 let request_id = VarInt::decode_moqt::<Wire>(buf)?;
2129 let namespace_prefix = TrackNamespace::decode_allow_empty_moqt::<Wire>(buf)?;
2130 let parameters = decode_parameters(buf)?;
2131 Ok(ControlMessage::SubscribeNamespace(SubscribeNamespace {
2132 request_id,
2133 namespace_prefix,
2134 parameters,
2135 }))
2136 }
2137 MessageType::SubscribeTracks => {
2138 let request_id = VarInt::decode_moqt::<Wire>(buf)?;
2139 let namespace_prefix = TrackNamespace::decode_allow_empty_moqt::<Wire>(buf)?;
2140 let parameters = decode_parameters(buf)?;
2141 Ok(ControlMessage::SubscribeTracks(SubscribeTracks {
2142 request_id,
2143 namespace_prefix,
2144 parameters,
2145 }))
2146 }
2147 MessageType::TrackStatus => {
2148 let request_id = VarInt::decode_moqt::<Wire>(buf)?;
2149 let track_namespace = TrackNamespace::decode_moqt::<Wire>(buf)?;
2150 let tn_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
2151 let track_name = read_bytes(buf, tn_len)?;
2152 check_full_track_name(&track_namespace, &track_name)?;
2153 let parameters = decode_parameters(buf)?;
2154 Ok(ControlMessage::TrackStatus(TrackStatus {
2155 request_id,
2156 track_namespace,
2157 track_name,
2158 parameters,
2159 }))
2160 }
2161 MessageType::Fetch => {
2162 let request_id = VarInt::decode_moqt::<Wire>(buf)?;
2163 let track_namespace = TrackNamespace::decode_moqt::<Wire>(buf)?;
2164 let tn_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
2165 let track_name = read_bytes(buf, tn_len)?;
2166 check_full_track_name(&track_namespace, &track_name)?;
2167 let parameters = decode_parameters(buf)?;
2168 Ok(ControlMessage::Fetch(Fetch {
2169 request_id,
2170 track_namespace,
2171 track_name,
2172 parameters,
2173 }))
2174 }
2175 MessageType::FetchOk => {
2176 if buf.remaining() < 1 {
2177 return Err(CodecError::UnexpectedEnd);
2178 }
2179 let end_of_track = buf.get_u8();
2180 let end_group = VarInt::decode_moqt::<Wire>(buf)?;
2181 let end_object = VarInt::decode_moqt::<Wire>(buf)?;
2182 let parameters = decode_parameters(buf)?;
2183 let track_properties = decode_track_properties(buf)?;
2184 Ok(ControlMessage::FetchOk(FetchOk {
2185 end_of_track,
2186 end_group,
2187 end_object,
2188 parameters,
2189 track_properties,
2190 }))
2191 }
2192 MessageType::PublishSkipped => {
2193 let namespace_suffix = TrackNamespace::decode_allow_empty_moqt::<Wire>(buf)?;
2194 let tn_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
2195 let track_name = read_bytes(buf, tn_len)?;
2196 check_full_track_name(&namespace_suffix, &track_name)?;
2197 Ok(ControlMessage::PublishSkipped(PublishSkipped { namespace_suffix, track_name }))
2198 }
2199 }
2200 }
2201
2202 pub fn message_type(&self) -> MessageType {
2203 match self {
2204 ControlMessage::Setup(_) => MessageType::Setup,
2205 ControlMessage::GoAway(_) => MessageType::GoAway,
2206 ControlMessage::RequestOk(_) => MessageType::RequestOk,
2207 ControlMessage::RequestError(_) => MessageType::RequestError,
2208 ControlMessage::Subscribe(_) => MessageType::Subscribe,
2209 ControlMessage::SubscribeOk(_) => MessageType::SubscribeOk,
2210 ControlMessage::RequestUpdate(_) => MessageType::RequestUpdate,
2211 ControlMessage::Publish(_) => MessageType::Publish,
2212 ControlMessage::PublishStateNotify(_) => MessageType::PublishStateNotify,
2213 ControlMessage::PublishDone(_) => MessageType::PublishDone,
2214 ControlMessage::PublishNamespace(_) => MessageType::PublishNamespace,
2215 ControlMessage::Namespace(_) => MessageType::Namespace,
2216 ControlMessage::NamespaceDone(_) => MessageType::NamespaceDone,
2217 ControlMessage::SubscribeNamespace(_) => MessageType::SubscribeNamespace,
2218 ControlMessage::SubscribeTracks(_) => MessageType::SubscribeTracks,
2219 ControlMessage::TrackStatus(_) => MessageType::TrackStatus,
2220 ControlMessage::Fetch(_) => MessageType::Fetch,
2221 ControlMessage::FetchOk(_) => MessageType::FetchOk,
2222 ControlMessage::PublishSkipped(_) => MessageType::PublishSkipped,
2223 }
2224 }
2225}
2226
2227#[cfg(test)]
2228mod tests {
2229 use super::*;
2230
2231 /// Frame `body` as a draft-21 control message of `type_id`, declaring
2232 /// `declared_len` rather than the body's real length. Used to build the
2233 /// mismatched frame the length rule is about.
2234 fn frame_with_declared_len(type_id: u64, declared_len: u16, body: &[u8]) -> Vec<u8> {
2235 let mut out = Vec::new();
2236 VarInt::from_u64_moqt(type_id).encode_moqt::<Wire>(&mut out);
2237 out.put_u16(declared_len);
2238 out.put_slice(body);
2239 out
2240 }
2241
2242 fn frame(type_id: u64, body: &[u8]) -> Vec<u8> {
2243 frame_with_declared_len(type_id, body.len() as u16, body)
2244 }
2245
2246 /// A SUBSCRIBE body: request id 1, namespace ("a"), track name "b", and
2247 /// `params` already encoded.
2248 fn subscribe_body(params: &[u8]) -> Vec<u8> {
2249 let mut body = vec![0x01, 0x01, 0x01, b'a', 0x01, b'b'];
2250 body.extend_from_slice(params);
2251 body
2252 }
2253
2254 /// Draft-21 Section 9: "If the length does not match the length of the
2255 /// Message Body, the receiver MUST close the session with a
2256 /// PROTOCOL_VIOLATION."
2257 ///
2258 /// Fails if `decode` parses this SUBSCRIBE and lets the two surplus bytes
2259 /// vanish.
2260 #[test]
2261 fn a_message_body_shorter_than_the_declared_length_is_refused() {
2262 let body = subscribe_body(&[0x00]);
2263 let mut junked = body.clone();
2264 junked.extend_from_slice(&[0xff, 0xff]);
2265 let bytes = frame_with_declared_len(0x03, (body.len() + 2) as u16, &junked);
2266
2267 let mut buf = &bytes[..];
2268 assert_eq!(
2269 ControlMessage::decode(&mut buf),
2270 Err(CodecError::ControlMessageLengthMismatch {
2271 declared: (body.len() + 2),
2272 detail: "its fields left bytes unread",
2273 })
2274 );
2275
2276 // The same body with an honest length still decodes, so the guard
2277 // rejects the mismatch and not the message.
2278 let honest = frame(0x03, &body);
2279 let mut buf = &honest[..];
2280 assert!(ControlMessage::decode(&mut buf).is_ok());
2281 }
2282
2283 /// Draft-21 Section 8.5: "The reason phrase length has a maximum value of
2284 /// 1024 bytes. If an endpoint receives a length exceeding the maximum, it
2285 /// MUST close the session with a PROTOCOL_VIOLATION".
2286 ///
2287 /// Fails if the decoder hands the 2000-byte phrase to the application.
2288 #[test]
2289 fn an_over_long_reason_phrase_is_refused_on_decode() {
2290 for (type_id, prefix) in [(0x05u64, vec![0x01, 0x00]), (0x0B, vec![0x01, 0x00])] {
2291 let mut body = prefix;
2292 let over = MAX_REASON_PHRASE_LENGTH + 976;
2293 VarInt::from_usize(over).encode_moqt::<Wire>(&mut body);
2294 body.extend(std::iter::repeat_n(b'x', over));
2295 let bytes = frame(type_id, &body);
2296
2297 let mut buf = &bytes[..];
2298 assert_eq!(
2299 ControlMessage::decode(&mut buf),
2300 Err(CodecError::ReasonPhraseTooLong),
2301 "message type 0x{type_id:x}"
2302 );
2303 }
2304 }
2305
2306 /// Draft-21 Section 9.2: "The maximum length of the New Session URI is
2307 /// 8,192 bytes. If an endpoint receives a length exceeding the maximum, it
2308 /// MUST close the session with a PROTOCOL_VIOLATION."
2309 ///
2310 /// Fails if the oversized URI reaches the application, where a migrating
2311 /// endpoint would follow it.
2312 #[test]
2313 fn an_over_long_goaway_uri_is_refused_on_decode() {
2314 let over = MAX_GOAWAY_URI_LENGTH + 808;
2315 let mut body = Vec::new();
2316 VarInt::from_usize(over).encode_moqt::<Wire>(&mut body);
2317 body.extend(std::iter::repeat_n(b'u', over));
2318 body.push(0x00); // timeout
2319 let bytes = frame(0x10, &body);
2320
2321 let mut buf = &bytes[..];
2322 assert_eq!(ControlMessage::decode(&mut buf), Err(CodecError::GoAwayUriTooLong));
2323 }
2324
2325 /// Draft-21 Section 9.20.9 (GROUP_ORDER): "The allowed values are Ascending
2326 /// (0x1) or Descending (0x2). If an endpoint receives a value outside this
2327 /// range, it MUST close the session with PROTOCOL_VIOLATION." Section
2328 /// 9.20.18 says the same of FORWARD with the values 0 and 1.
2329 ///
2330 /// Fails if the out-of-range byte is handed up as an ordinary parameter.
2331 #[test]
2332 fn a_uint8_parameter_outside_its_range_is_refused() {
2333 // key, rejected value, accepted value
2334 let cases = [(0x22u8, 7u8, 2u8), (0x10, 9, 1)];
2335 for (key, bad, good) in cases {
2336 let bytes = frame(0x03, &subscribe_body(&[0x01, key, bad]));
2337 let mut buf = &bytes[..];
2338 assert_eq!(
2339 ControlMessage::decode(&mut buf),
2340 Err(CodecError::ParameterValueOutOfRange { key: key as u64, value: bad as u64 }),
2341 "parameter 0x{key:x} value {bad}"
2342 );
2343
2344 let bytes = frame(0x03, &subscribe_body(&[0x01, key, good]));
2345 let mut buf = &bytes[..];
2346 assert!(
2347 ControlMessage::decode(&mut buf).is_ok(),
2348 "parameter 0x{key:x} value {good} should still decode"
2349 );
2350 }
2351 }
2352
2353 /// SUBSCRIBER_PRIORITY (0x20) is a uint8 with no restricted range, so it
2354 /// must keep accepting the whole 0-255 span. This is the negative half of
2355 /// the range check: a table that over-reached would fail here.
2356 #[test]
2357 fn subscriber_priority_still_accepts_the_whole_byte_range() {
2358 for value in [0u8, 1, 2, 128, 255] {
2359 let bytes = frame(0x03, &subscribe_body(&[0x01, 0x20, value]));
2360 let mut buf = &bytes[..];
2361 assert!(ControlMessage::decode(&mut buf).is_ok(), "priority {value}");
2362 }
2363 }
2364
2365 fn param(key: u64, value: &[u8]) -> KeyValuePair {
2366 KeyValuePair { key: VarInt::from_u64_moqt(key), value: KvpValue::Bytes(value.to_vec()) }
2367 }
2368
2369 /// Draft-21 Section 9.20.18: "The LARGEST_OBJECT parameter (Parameter Type
2370 /// 0x9) is a Location." Section 9.20 defines Location as "Two consecutive
2371 /// varints (Group, Object)" — the value carries no length of its own.
2372 ///
2373 /// The frame below is built from the draft rather than from this encoder:
2374 /// REQUEST_OK, four body bytes, one parameter, type delta `0x09`, then the
2375 /// two varints `0x0a` and `0x03` for Location (10, 3). A length-prefixed
2376 /// spelling would need a fifth byte.
2377 ///
2378 /// Fails if `0x09` is read as Length-prefixed: the decoder then takes the
2379 /// group varint `0x0a` as a value length of 10 and runs off the end of a
2380 /// four-byte body. [`a_location_does_not_eat_the_block_that_follows_it`]
2381 /// guards the same misreading.
2382 #[test]
2383 fn largest_object_is_two_bare_varints() {
2384 let body = [0x01, 0x09, 0x0a, 0x03];
2385 let bytes = frame(0x07, &body);
2386
2387 let msg = ControlMessage::decode(&mut &bytes[..]).expect("spec-correct frame must decode");
2388 let ControlMessage::RequestOk(ok) = &msg else {
2389 panic!("expected REQUEST_OK, got {msg:?}")
2390 };
2391 assert_eq!(ok.parameters, vec![param(0x09, &[0x0a, 0x03])]);
2392 assert!(ok.track_properties.is_empty(), "the four body bytes are all parameter");
2393
2394 let mut out = Vec::new();
2395 msg.encode(&mut out).expect("re-encode");
2396 assert_eq!(out, bytes, "the value must go back out as the two bare varints it came in as");
2397 }
2398
2399 /// A Location value the encoder was handed but the decoder could not read
2400 /// back is refused on the way out, not written.
2401 ///
2402 /// LARGEST_OBJECT carries no length of its own — that is the whole point
2403 /// of the encoding — so `encode_parameters` writes its bytes verbatim. A
2404 /// value built in memory rather than decoded is under no obligation to be
2405 /// two varints, and without this check the codec answers `Ok(())` and puts
2406 /// a frame on the wire that `ControlMessage::decode` then refuses. One
2407 /// varint short and one varint long are the two ways to get it wrong.
2408 #[test]
2409 fn a_location_value_that_is_not_two_varints_is_refused_on_encode() {
2410 for (label, value) in
2411 [("one varint", vec![0x0a]), ("three varints", vec![0x0a, 0x03, 0x05])]
2412 {
2413 let msg = ControlMessage::RequestOk(RequestOk {
2414 parameters: vec![param(0x09, &value)],
2415 track_properties: Vec::new(),
2416 });
2417 let mut out = Vec::new();
2418 assert!(
2419 msg.encode(&mut out).is_err(),
2420 "LARGEST_OBJECT of {label} must not encode: the decoder cannot read it back"
2421 );
2422 }
2423
2424 // The well-formed value still goes out, so the check refuses the
2425 // malformed case and not the encoding itself.
2426 let msg = ControlMessage::RequestOk(RequestOk {
2427 parameters: vec![param(0x09, &[0x0a, 0x03])],
2428 track_properties: Vec::new(),
2429 });
2430 let mut out = Vec::new();
2431 msg.encode(&mut out).expect("a Location of exactly two varints must still encode");
2432 ControlMessage::decode(&mut &out[..]).expect("and must decode back");
2433 }
2434
2435 /// The same Location read through a message that carries other fields
2436 /// after it, so a stray length byte cannot hide in a trailing block.
2437 ///
2438 /// SUBSCRIBE_OK is track alias `0x05`, then the parameters, then the track
2439 /// properties. With LARGEST_OBJECT (10, 3) and one property
2440 /// (OBJECT_DELIVERY_TIMEOUT, type `0x02`, 5000ms as the two-byte varint
2441 /// `0x93 0x88`), the body is `05 01 09 0a 03 02 93 88`.
2442 #[test]
2443 fn a_location_does_not_eat_the_block_that_follows_it() {
2444 let body = [0x05, 0x01, 0x09, 0x0a, 0x03, 0x02, 0x93, 0x88];
2445 let bytes = frame(0x04, &body);
2446
2447 let msg = ControlMessage::decode(&mut &bytes[..]).expect("spec-correct frame must decode");
2448 let ControlMessage::SubscribeOk(ok) = &msg else {
2449 panic!("expected SUBSCRIBE_OK, got {msg:?}")
2450 };
2451 assert_eq!(ok.parameters, vec![param(0x09, &[0x0a, 0x03])]);
2452 assert_eq!(
2453 ok.track_properties,
2454 vec![KeyValuePair {
2455 key: VarInt::from_u64_moqt(0x02),
2456 value: KvpValue::Varint(VarInt::from_u64_moqt(5000)),
2457 }]
2458 );
2459
2460 let mut out = Vec::new();
2461 msg.encode(&mut out).expect("re-encode");
2462 assert_eq!(out, bytes);
2463 }
2464
2465 /// Draft-21 Section 9.20.21: the TRACK_NAMESPACE_PREFIX parameter
2466 /// (Parameter Type 0x34) "uses the Track Namespace encoding described in
2467 /// Section 8.7" — a varint field count followed by that many
2468 /// length-prefixed fields, and nothing in front of it. That encoding is not
2469 /// one of the four Section 9.20 lists, so it cannot be assumed to be
2470 /// Length-prefixed by default.
2471 ///
2472 /// The frame below is built from Section 8.7: REQUEST_UPDATE for request
2473 /// `7`, one parameter, type delta `0x34`, then the namespace ("live",
2474 /// "sports") as `02 04 "live" 06 "sports"`. Sixteen body bytes; a
2475 /// length-prefixed spelling would need a seventeenth for the outer length.
2476 ///
2477 /// Fails if `0x34` is read as Length-prefixed: the field count `0x02` is
2478 /// then taken as an outer length of two bytes, leaving eleven bytes of
2479 /// namespace unread, which the draft-21 Section 9 body-length check refuses.
2480 /// That check is not a safety net here. Where the surplus lands inside the
2481 /// declared body — as in
2482 /// [`an_empty_track_namespace_prefix_is_one_zero_byte`], whose namespace is
2483 /// one byte long — the misread is silent, and that test catches it.
2484 #[test]
2485 fn track_namespace_prefix_is_a_bare_track_namespace() {
2486 let namespace: Vec<u8> = [&[0x02, 0x04][..], b"live", &[0x06][..], b"sports"].concat();
2487 assert_eq!(namespace.len(), 13);
2488
2489 let body: Vec<u8> = [&[0x07, 0x01, 0x34][..], &namespace].concat();
2490 assert_eq!(body.len(), 16);
2491 let bytes = frame(0x02, &body);
2492
2493 let msg = ControlMessage::decode(&mut &bytes[..]).expect("spec-correct frame must decode");
2494 let ControlMessage::RequestUpdate(update) = &msg else {
2495 panic!("expected REQUEST_UPDATE, got {msg:?}")
2496 };
2497 assert_eq!(update.request_id.into_inner(), 7);
2498 assert_eq!(update.parameters, vec![param(0x34, &namespace)]);
2499
2500 let mut out = Vec::new();
2501 msg.encode(&mut out).expect("re-encode");
2502 assert_eq!(out, bytes, "no outer length may appear in front of the Track Namespace");
2503 }
2504
2505 /// An empty prefix is a legal Track Namespace: Section 2.4.1 puts one at
2506 /// "between 0 and 32 Track Namespace Fields". On the wire that is the
2507 /// single byte `0x00`, and it must not be confused with a length-prefixed
2508 /// value of zero bytes.
2509 #[test]
2510 fn an_empty_track_namespace_prefix_is_one_zero_byte() {
2511 let body = [0x07, 0x01, 0x34, 0x00];
2512 let bytes = frame(0x02, &body);
2513
2514 let msg = ControlMessage::decode(&mut &bytes[..]).expect("empty prefix must decode");
2515 let ControlMessage::RequestUpdate(update) = &msg else {
2516 panic!("expected REQUEST_UPDATE, got {msg:?}")
2517 };
2518 assert_eq!(update.parameters, vec![param(0x34, &[0x00])]);
2519
2520 let mut out = Vec::new();
2521 msg.encode(&mut out).expect("re-encode");
2522 assert_eq!(out, bytes);
2523 }
2524}