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