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