moqtap_codec/types.rs
1use crate::varint::{MoqtProfile, VarInt};
2use bytes::{Buf, BufMut};
3
4/// Reserve space for `count` items without trusting `count`.
5///
6/// A count-prefixed list declares how many items follow, and the declaration
7/// arrives before any of them. Passing it straight to [`Vec::with_capacity`]
8/// hands an unauthenticated peer control of one allocation: a varint holds
9/// values up to 2^62-1, and asking for that many elements aborts the process
10/// with `capacity overflow` before a single item has been read. On a control
11/// stream the first message is a peer's SETUP, so this is reachable before
12/// anything has been negotiated or authenticated.
13///
14/// Every item in these lists occupies at least one byte on the wire, so the
15/// bytes still in `buf` are a true upper bound on how many can really follow.
16/// A well-formed message is unaffected — its count is far below its length —
17/// and a malformed one allocates no more than it actually sent.
18///
19/// This bounds the allocation only. The decode loop still fails on the first
20/// item that is not there, which is what turns an over-long count into an
21/// error rather than a short read.
22#[inline]
23pub fn reserve_bounded<T>(count: usize, buf: &impl Buf) -> Vec<T> {
24 Vec::with_capacity(count.min(buf.remaining()))
25}
26
27/// Read exactly `len` bytes from `buf`, returning them as a `Vec<u8>`.
28#[inline]
29#[allow(clippy::uninit_vec)]
30pub fn read_bytes(buf: &mut impl Buf, len: usize) -> Result<Vec<u8>, crate::error::CodecError> {
31 if buf.remaining() < len {
32 return Err(crate::error::CodecError::UnexpectedEnd);
33 }
34 let mut v = Vec::with_capacity(len);
35 // Safety: `set_len(len)` with capacity `len` exposes `len` uninitialized
36 // `u8`s. `copy_to_slice` immediately overwrites all of them before any
37 // read. `u8` has no drop, so no leaks on panic beyond the `Vec` itself.
38 unsafe {
39 v.set_len(len);
40 }
41 buf.copy_to_slice(&mut v);
42 Ok(v)
43}
44
45/// Refuse a range whose end is earlier than its start, where the End Group is
46/// the last Group ID and the End Object is the last Object ID plus one.
47///
48/// This is the shape FETCH carries on every draft: the End Group field is "the
49/// end Group ID" and the End Object field is "The end Object ID, plus 1. A
50/// value of 0 means the entire group is requested." A zero End Object therefore
51/// places no upper bound inside the end group and cannot make the range empty,
52/// so it is exempt.
53///
54/// Draft-07 Section 6.4 gives the SUBSCRIBE AbsoluteRange filter the same two
55/// fields with the same conventions, and states the rule in the same words, so
56/// that filter is checked here too.
57///
58/// # Errors
59///
60/// [`crate::error::CodecError::InvalidRange`] if the end is earlier than the
61/// start, reporting both ends as they appear on the wire.
62pub fn check_location_range(
63 start_group: u64,
64 start_object: u64,
65 end_group: u64,
66 end_object: u64,
67) -> Result<(), crate::error::CodecError> {
68 let ends_early = end_group < start_group
69 || (end_group == start_group && end_object != 0 && end_object <= start_object);
70 if ends_early {
71 return Err(crate::error::CodecError::InvalidRange(
72 start_group,
73 start_object,
74 end_group,
75 end_object,
76 ));
77 }
78 Ok(())
79}
80
81/// Refuse a subscription whose End Group is earlier than its start group, where
82/// the End Group is inclusive and always present.
83///
84/// Drafts 08 through 14 describe the SUBSCRIBE AbsoluteRange field as "the end
85/// Group ID, inclusive. Only present for the 'AbsoluteRange' filter type", so
86/// there is no value that means "no end" and nothing to exempt. The rule is
87/// Section 7.4 through Section 9.7: "End Group MUST specify the same or a
88/// larger Group than specified in Start."
89///
90/// Only the groups are compared. Those drafts have no End Object on SUBSCRIBE,
91/// so an end group equal to the start group is the whole of that group and is
92/// what the draft calls out as legal: "If the specified End Group is the same
93/// group specified in Start, the remainder of that Group passes the filter."
94///
95/// # Errors
96///
97/// [`crate::error::CodecError::InvalidRange`] if the end group is smaller than
98/// the start group.
99pub fn check_group_range(start_group: u64, end_group: u64) -> Result<(), crate::error::CodecError> {
100 if end_group < start_group {
101 return Err(crate::error::CodecError::InvalidRange(start_group, 0, end_group, 0));
102 }
103 Ok(())
104}
105
106/// Refuse a SUBSCRIBE_UPDATE whose End Group is earlier than its start group.
107///
108/// SUBSCRIBE_UPDATE spells the field differently from SUBSCRIBE on every draft
109/// that has both: "End Group: The end Group ID, plus 1. A value of 0 means the
110/// subscription is open-ended." A zero is an open end and places no bound at
111/// all, so it is exempt.
112///
113/// The comparison is deliberately the literal one the draft states - "Like
114/// SUBSCRIBE, End Group MUST be greater than or equal to the Group specified in
115/// Start" - against the field as it arrives, without first undoing the plus
116/// one. Undoing it would make an End Group equal to the start group a refusal,
117/// and the draft's sentence does not say that, so a frame the draft may permit
118/// would be refused on a reading rather than on a rule.
119///
120/// # Errors
121///
122/// [`crate::error::CodecError::InvalidRange`] if a non-zero end group is
123/// smaller than the start group.
124pub fn check_open_ended_group_range(
125 start_group: u64,
126 end_group: u64,
127) -> Result<(), crate::error::CodecError> {
128 if end_group != 0 && end_group < start_group {
129 return Err(crate::error::CodecError::InvalidRange(start_group, 0, end_group, 0));
130 }
131 Ok(())
132}
133
134/// Track Namespace: an ordered set of Track Namespace Fields.
135///
136/// The permitted field count is not the same on every draft, so this type does
137/// not carry one. Section 2.4.1 "Track Naming" calls a Track Namespace "an
138/// ordered N-tuple of bytes where N can be between 1 and 32" on drafts 07
139/// through 14, "an ordered set of between 1 and 32 Track Namespace Fields" on
140/// drafts 15 and 16, and "an ordered set of between 0 and 32 Track Namespace
141/// Fields" from draft-17 on. [`TrackNamespaceRules`] carries that answer per
142/// draft, along with the two rules Section 2.4.1 later adds about what a single
143/// field may contain.
144#[derive(Debug, Clone, PartialEq, Eq)]
145pub struct TrackNamespace(pub Vec<Vec<u8>>);
146
147/// What a MoQ Transport draft states about a Track Namespace in Section 2.4.1
148/// "Track Naming".
149///
150/// The rules arrive at different drafts and one of them is later withdrawn, so
151/// any single rule applied to all thirteen drafts is wrong somewhere:
152///
153/// - *At most 32 Track Namespace Fields.* Stated by every draft from 07 on, and
154/// the only field-count rule drafts 17 and later state at all. It has never
155/// moved, so it is not carried here; [`MAX_NAMESPACE_TUPLE_SIZE`] holds it and
156/// every reader applies it.
157///
158/// - *At least one Track Namespace Field.* Drafts 07 through 16 define a Track
159/// Namespace as "between 1 and 32", and drafts 08 through 16 add "If an
160/// endpoint receives a Track Namespace tuple with an N of 0 or more than 32,
161/// it MUST close the session with a Protocol Violation" (draft-15 and later
162/// word it as "consisting of 0 or greater than 32 Track Namespace Fields").
163/// Draft-17 redefines the type as "between 0 and 32" and drops that half of
164/// the sentence.
165///
166/// An individual message may lower the minimum below what Section 2.4.1 says.
167/// Draft-16 is the first to describe the SUBSCRIBE_NAMESPACE Track Namespace
168/// Prefix as "a Track Namespace structure as described in Section 2.4.1 with
169/// between 0 and 32 Track Namespace Fields", and it drops the sentence that
170/// drafts 08 through 15 carry about a prefix of 0 fields closing the session.
171/// [`min_fields`](Self::min_fields) is therefore a value a call site can
172/// lower, not a function of the draft alone.
173///
174/// - *Each field at least one byte.* Draft-16 is the first to say "Each Track
175/// Namespace Field Value MUST contain at least one byte. If an endpoint
176/// receives a Track Namespace Field with a Track Namespace Field Length of 0,
177/// it MUST close the session with a PROTOCOL_VIOLATION." Drafts 07 through 15
178/// say nothing about it, and refusing an empty field there would reject
179/// traffic those drafts permit.
180///
181/// - *A Track Namespace of at most 4,096 bytes.* Draft-16 is the first to say
182/// "The length of a Track Namespace is the sum of the Track Namespace Field
183/// Length fields... If an endpoint receives a Track Namespace or a Full Track
184/// Name exceeding 4,096 bytes, it MUST close the session with a
185/// PROTOCOL_VIOLATION." Drafts 11 through 15 cap only the Full Track Name, so
186/// on those a namespace is bounded only through the Track Name beside it and
187/// this reader cannot settle it alone. Drafts 07 through 10 state no cap.
188///
189/// [`MAX_NAMESPACE_TUPLE_SIZE`]: crate::error::MAX_NAMESPACE_TUPLE_SIZE
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191pub struct TrackNamespaceRules {
192 /// Fewest Track Namespace Fields this position accepts.
193 pub min_fields: usize,
194 /// Whether a Track Namespace Field Length of 0 must be refused.
195 pub reject_empty_field: bool,
196 /// Cap on the sum of the Track Namespace Field Length fields, for the drafts
197 /// that bound a Track Namespace on its own.
198 ///
199 /// `None` where the draft bounds only the Full Track Name, which no reader
200 /// of the namespace alone can settle: the Track Name arrives beside it in
201 /// the message, and the two lengths are summed there.
202 pub max_namespace_bytes: Option<usize>,
203}
204
205impl TrackNamespaceRules {
206 /// The rules MoQ Transport draft `draft` states for a Track Namespace.
207 ///
208 /// `draft` is the draft number, 7 through 19. A larger number is answered
209 /// with the newest rules, which have not moved since draft-17.
210 ///
211 /// This is the Section 2.4.1 answer, which is what a position accepts unless
212 /// the message that carries it says otherwise. A message that is looser
213 /// about the count — draft-16's SUBSCRIBE_NAMESPACE prefix is — overrides
214 /// [`min_fields`](Self::min_fields) on the value returned here, so that the
215 /// content rules it does not restate still come from its own draft.
216 pub const fn for_draft(draft: u8) -> Self {
217 TrackNamespaceRules {
218 min_fields: if draft >= 17 { 0 } else { 1 },
219 reject_empty_field: draft >= 16,
220 max_namespace_bytes: if draft >= 16 {
221 Some(crate::error::MAX_FULL_TRACK_NAME_LENGTH)
222 } else {
223 None
224 },
225 }
226 }
227}
228
229/// Full Track Name: a Track Namespace and the Track Name within it.
230///
231/// Drafts 11 and later cap the pair: "The maximum total length of a Full Track
232/// Name is 4,096 bytes... computed as the sum of the Track Namespace Field
233/// Length fields and the Track Name Length field." Drafts 07 through 10 state no
234/// cap. That sum spans two fields that arrive separately, so it is settled where
235/// a message decodes both, with [`TrackNamespace::field_bytes_len`] supplying
236/// the namespace half.
237#[derive(Debug, Clone, PartialEq, Eq)]
238pub struct FullTrackName {
239 /// The track namespace tuple.
240 pub namespace: TrackNamespace,
241 /// The track name within the namespace.
242 pub track_name: Vec<u8>,
243}
244
245/// Location within a track: (Group, Object).
246#[derive(Debug, Clone, Copy, PartialEq, Eq)]
247pub struct Location {
248 /// Group identifier.
249 pub group: VarInt,
250 /// Object identifier within the group.
251 pub object: VarInt,
252}
253
254/// Object status values, from MoQ Transport draft-14 Section 10.2.1.1
255/// "Object Status".
256///
257/// The draft assigns 0x0, 0x1, 0x3 and 0x4; 0x2 is unassigned and
258/// [`ObjectStatus::from_u8`] answers `None` for it. Each `draftNN` module
259/// carries its own `ObjectStatus`, because the assigned set moves between
260/// drafts: drafts 08 through 10 also assign 0x5, and drafts 16 onward drop 0x1
261/// and assign only 0x0, 0x3 and 0x4.
262#[derive(Debug, Clone, Copy, PartialEq, Eq)]
263#[repr(u8)]
264pub enum ObjectStatus {
265 /// Object payload follows normally.
266 Normal = 0x0,
267 /// The referenced object does not exist at any publisher and will not be
268 /// published in the future.
269 DoesNotExist = 0x1,
270 /// Last object in the group.
271 EndOfGroup = 0x3,
272 /// Last object in the track.
273 EndOfTrack = 0x4,
274}
275
276/// Group ordering preference.
277#[derive(Debug, Clone, Copy, PartialEq, Eq)]
278#[repr(u8)]
279pub enum GroupOrder {
280 /// Publisher determines the order.
281 Publisher = 0x0,
282 /// Groups delivered in ascending order.
283 Ascending = 0x1,
284 /// Groups delivered in descending order.
285 Descending = 0x2,
286}
287
288/// Forwarding preference for objects.
289#[derive(Debug, Clone, Copy, PartialEq, Eq)]
290#[repr(u8)]
291pub enum ForwardingPreference {
292 /// Object forwarding (sent on a subgroup stream).
293 Object = 0x0,
294 /// Datagram forwarding (sent as a QUIC datagram).
295 Datagram = 0x1,
296}
297
298/// Whether content exists (used in SUBSCRIBE_OK).
299#[derive(Debug, Clone, Copy, PartialEq, Eq)]
300#[repr(u8)]
301pub enum ContentExists {
302 /// No largest location is provided.
303 NoLargestLocation = 0,
304 /// A largest location follows.
305 HasLargestLocation = 1,
306}
307
308/// Forward state (0 = don't forward, 1 = forward).
309#[derive(Debug, Clone, Copy, PartialEq, Eq)]
310#[repr(u8)]
311pub enum Forward {
312 /// Do not forward.
313 DontForward = 0,
314 /// Forward enabled.
315 Forward = 1,
316}
317
318/// Subscription filter types, named as draft-14 names them.
319///
320/// Every draft that has this field assigns the same four numbers, but 0x1 does
321/// not mean the same thing on all of them, and the variant here carries the
322/// later meaning. Drafts 07 and 08 call 0x1 "Latest Group": an open-ended
323/// subscription starting at the beginning of the *current* group. Drafts 09 and
324/// 10 withdraw the value, and their decoders refuse it. Drafts 11 and later
325/// call it "Next Group Start", which begins one group later. A caller reading
326/// [`FilterType::NextGroupStart`] off a draft-07 or draft-08 SUBSCRIBE has the
327/// right number and the wrong name.
328#[derive(Debug, Clone, Copy, PartialEq, Eq)]
329#[repr(u8)]
330pub enum FilterType {
331 /// Start from the next group on drafts 11 and later; the beginning of the
332 /// current group on drafts 07 and 08, which call the same value "Latest
333 /// Group". Not assigned on drafts 09 and 10.
334 NextGroupStart = 0x1,
335 /// Start from the largest available object.
336 LargestObject = 0x2,
337 /// Start from an absolute location.
338 AbsoluteStart = 0x3,
339 /// Absolute range with start and end locations.
340 AbsoluteRange = 0x4,
341}
342
343/// Authorization token alias types.
344#[derive(Debug, Clone, Copy, PartialEq, Eq)]
345#[repr(u8)]
346pub enum TokenAliasType {
347 /// Delete a previously registered alias.
348 Delete = 0x0,
349 /// Register a new alias.
350 Register = 0x1,
351 /// Use an existing alias.
352 UseAlias = 0x2,
353 /// Use a literal token value.
354 UseValue = 0x3,
355}
356
357impl TrackNamespace {
358 /// Encode the namespace tuple into the buffer.
359 pub fn encode(&self, buf: &mut impl BufMut) {
360 VarInt::from_usize(self.0.len()).encode(buf);
361 for elem in &self.0 {
362 VarInt::from_usize(elem.len()).encode(buf);
363 buf.put_slice(elem);
364 }
365 }
366
367 /// Decode a namespace tuple under the rules every draft from 07 through 16
368 /// shares.
369 ///
370 /// All ten of those drafts define a Track Namespace as "between 1 and 32"
371 /// fields, so the count is held to that and nothing else is. This one reader
372 /// serves all of them and cannot tell them apart, while the empty-field rule
373 /// and the 4,096-byte namespace cap both arrive in draft-16: applying either
374 /// here would refuse namespaces drafts 07 through 15 permit. A call site that
375 /// knows which draft it is decoding reaches those rules through
376 /// [`decode_rules`](Self::decode_rules) with
377 /// [`TrackNamespaceRules::for_draft`].
378 ///
379 /// Drafts 17 and later read namespaces through
380 /// [`decode_moqt`](Self::decode_moqt), which uses the other varint encoding.
381 pub fn decode(buf: &mut impl Buf) -> Result<Self, crate::error::CodecError> {
382 Self::decode_rules(
383 buf,
384 TrackNamespaceRules {
385 min_fields: 1,
386 reject_empty_field: false,
387 max_namespace_bytes: None,
388 },
389 )
390 }
391
392 /// Decode a draft-16 Track Namespace that may have zero fields.
393 ///
394 /// Draft-16 is the only draft before 17 with a namespace position that
395 /// permits the empty set. Its NAMESPACE and NAMESPACE_DONE carry "only the
396 /// namespace tuples after the 'Track Namespace Prefix'", which is nothing at
397 /// all once the prefix names the whole namespace, and its
398 /// SUBSCRIBE_NAMESPACE describes the prefix as "between 0 and 32 Track
399 /// Namespace Fields". Drafts 07 through 15 put the minimum at one field
400 /// everywhere, drafts 08 through 15 spelling out that zero closes the
401 /// session, so none of them has any use for this reader.
402 ///
403 /// Because draft-16 is its only draft, the fields are held to both rules
404 /// draft-16 Section 2.4.1 states about their contents: no field of length
405 /// zero, and at most 4,096 bytes summed across the fields. A namespace being
406 /// read for an earlier draft belongs in [`decode`](Self::decode), which
407 /// states neither.
408 pub fn decode_allow_empty(buf: &mut impl Buf) -> Result<Self, crate::error::CodecError> {
409 Self::decode_rules(
410 buf,
411 TrackNamespaceRules { min_fields: 0, ..TrackNamespaceRules::for_draft(16) },
412 )
413 }
414
415 /// Decode a namespace tuple, holding it to `rules`.
416 ///
417 /// The reader for drafts 07 through 16, which write both the field count and
418 /// every field length as a QUIC variable-length integer (RFC 9000 Section
419 /// 16). Drafts 17 and later changed that encoding and read namespaces
420 /// through [`decode_moqt`](Self::decode_moqt) instead.
421 ///
422 /// The upper bound on the field count is not part of `rules`: every draft
423 /// from 07 on puts it at 32 and none has moved it, so it is applied here
424 /// unconditionally. The byte cap is applied to the running sum as the field
425 /// lengths are read, so a namespace that overruns is refused before its
426 /// remaining fields are allocated.
427 pub fn decode_rules(
428 buf: &mut impl Buf,
429 rules: TrackNamespaceRules,
430 ) -> Result<Self, crate::error::CodecError> {
431 let n = VarInt::decode(buf)?.into_inner() as usize;
432 if n < rules.min_fields || n > crate::error::MAX_NAMESPACE_TUPLE_SIZE {
433 return Err(crate::error::CodecError::InvalidNamespaceTupleSize(n));
434 }
435 let mut elements = Vec::with_capacity(n);
436 let mut total = 0usize;
437 for _ in 0..n {
438 let len = VarInt::decode(buf)?.into_inner() as usize;
439 if len == 0 && rules.reject_empty_field {
440 return Err(crate::error::CodecError::EmptyNamespaceField);
441 }
442 total = total.saturating_add(len);
443 if let Some(max) = rules.max_namespace_bytes {
444 if total > max {
445 return Err(crate::error::CodecError::TrackNameTooLong);
446 }
447 }
448 elements.push(read_bytes(buf, len)?);
449 }
450 Ok(TrackNamespace(elements))
451 }
452
453 /// Encode the namespace tuple using the MoQT varint (drafts 17 and later).
454 pub fn encode_moqt<P: MoqtProfile>(&self, buf: &mut impl BufMut) {
455 VarInt::from_usize(self.0.len()).encode_moqt::<P>(buf);
456 for elem in &self.0 {
457 VarInt::from_usize(elem.len()).encode_moqt::<P>(buf);
458 buf.put_slice(elem);
459 }
460 }
461
462 /// Decode a namespace tuple using the MoQT varint (drafts 17 and later).
463 ///
464 /// A field count of zero is accepted. Drafts 17, 18 and 19 all define a
465 /// Track Namespace as "an ordered set of between 0 and 32 Track Namespace
466 /// Fields", and state only one field-count violation: "If an endpoint
467 /// receives a Track Namespace consisting of greater than 32 Track Namespace
468 /// Fields, it MUST close the session with a PROTOCOL_VIOLATION." Draft-16
469 /// Section 2.4.1 says "between 1 and 32" instead, which is why the pre-17
470 /// [`decode`](Self::decode) still refuses an empty tuple and this does not.
471 ///
472 /// Refusing the empty tuple here would also have made the codec emit frames
473 /// it will not read: [`encode_moqt`](Self::encode_moqt) writes a zero field
474 /// count without complaint, so the two directions disagreed about a shape
475 /// the draft permits.
476 ///
477 /// This leaves [`decode_moqt`](Self::decode_moqt) and
478 /// [`decode_allow_empty_moqt`](Self::decode_allow_empty_moqt) accepting the
479 /// same field counts on drafts 17 and later. Both are kept because the
480 /// distinction is still real one draft earlier, where [`decode`](Self::decode)
481 /// and [`decode_allow_empty`](Self::decode_allow_empty) part company over it,
482 /// and because a call site naming the one it means says which rule it is
483 /// relying on.
484 pub fn decode_moqt<P: MoqtProfile>(
485 buf: &mut impl Buf,
486 ) -> Result<Self, crate::error::CodecError> {
487 Self::decode_allow_empty_moqt::<P>(buf)
488 }
489
490 /// Decode a MoQT namespace tuple that may have zero elements (suffix types).
491 pub fn decode_allow_empty_moqt<P: MoqtProfile>(
492 buf: &mut impl Buf,
493 ) -> Result<Self, crate::error::CodecError> {
494 let n = VarInt::decode_moqt::<P>(buf)?.into_inner() as usize;
495 if n > crate::error::MAX_NAMESPACE_TUPLE_SIZE {
496 return Err(crate::error::CodecError::InvalidNamespaceTupleSize(n));
497 }
498 Self::decode_elements_moqt::<P>(buf, n)
499 }
500
501 /// Read `n` Track Namespace Fields, holding them to the two rules Section
502 /// 2.4.1 states about their contents.
503 ///
504 /// "Each Track Namespace Field Value MUST contain at least one byte. If an
505 /// endpoint receives a Track Namespace Field with a Track Namespace Field
506 /// Length of 0, it MUST close the session with a PROTOCOL_VIOLATION." An
507 /// empty field is not the same namespace as no field, but the two render
508 /// identically and an empty field makes two distinct namespaces compare
509 /// equal under the prefix-matching rules, which is a routing hazard at a
510 /// relay.
511 ///
512 /// "The length of a Track Namespace is the sum of the Track Namespace Field
513 /// Length fields... If an endpoint receives a Track Namespace or a Full
514 /// Track Name exceeding 4,096 bytes, it MUST close the session with a
515 /// PROTOCOL_VIOLATION." The sum is kept as the fields are read, so a
516 /// namespace that overruns is refused before its remaining fields are
517 /// allocated. The Full Track Name half of that sentence needs the Track
518 /// Name, which lives in the message rather than here, and is checked where
519 /// the two are decoded together.
520 ///
521 /// Drafts 07 through 16 read their fields through
522 /// [`decode_rules`](Self::decode_rules), which applies whichever of these two
523 /// rules the caller's draft states: draft-16 states both, and no draft before
524 /// it states either.
525 fn decode_elements_moqt<P: MoqtProfile>(
526 buf: &mut impl Buf,
527 n: usize,
528 ) -> Result<Self, crate::error::CodecError> {
529 let mut elements = Vec::with_capacity(n);
530 let mut total = 0usize;
531 for _ in 0..n {
532 let len = VarInt::decode_moqt::<P>(buf)?.into_inner() as usize;
533 if len == 0 {
534 return Err(crate::error::CodecError::EmptyNamespaceField);
535 }
536 total = total.saturating_add(len);
537 if total > crate::error::MAX_FULL_TRACK_NAME_LENGTH {
538 return Err(crate::error::CodecError::TrackNameTooLong);
539 }
540 elements.push(read_bytes(buf, len)?);
541 }
542 Ok(TrackNamespace(elements))
543 }
544
545 /// The sum of this namespace's Track Namespace Field Length fields.
546 ///
547 /// Section 2.4.1 defines both caps in terms of this sum: a Track Namespace
548 /// is capped at 4,096 bytes on its own, and a Full Track Name is capped at
549 /// this plus the Track Name Length.
550 pub fn field_bytes_len(&self) -> usize {
551 self.0.iter().map(|field| field.len()).sum()
552 }
553
554 /// Hold this namespace to the rules drafts 17 and later state in Section
555 /// 2.4.1, before it goes on the wire.
556 ///
557 /// The encode-side mirror of
558 /// [`decode_allow_empty_moqt`](Self::decode_allow_empty_moqt): at most 32 fields,
559 /// no empty field, and at most 4,096 bytes of field content. Without it the
560 /// codec would emit namespaces its own decoder refuses, and hand a
561 /// conforming peer a reason to close the session.
562 pub fn validate_moqt(&self) -> Result<(), crate::error::CodecError> {
563 self.validate(TrackNamespaceRules::for_draft(17))
564 }
565
566 /// Hold this namespace to `rules` before it goes on the wire.
567 ///
568 /// The encode-side mirror of [`decode_rules`](Self::decode_rules), taking
569 /// the same description of what a draft permits so that the two directions
570 /// cannot drift apart. A codec that writes what it will not read hands a
571 /// conforming peer a reason to close the session, and finds out only when
572 /// the peer does.
573 ///
574 /// The upper bound on the field count is not part of `rules` here either:
575 /// every draft from 07 on puts it at 32.
576 pub fn validate(&self, rules: TrackNamespaceRules) -> Result<(), crate::error::CodecError> {
577 if self.0.len() < rules.min_fields || self.0.len() > crate::error::MAX_NAMESPACE_TUPLE_SIZE
578 {
579 return Err(crate::error::CodecError::InvalidNamespaceTupleSize(self.0.len()));
580 }
581 if rules.reject_empty_field && self.0.iter().any(|field| field.is_empty()) {
582 return Err(crate::error::CodecError::EmptyNamespaceField);
583 }
584 if let Some(max) = rules.max_namespace_bytes {
585 if self.field_bytes_len() > max {
586 return Err(crate::error::CodecError::TrackNameTooLong);
587 }
588 }
589 Ok(())
590 }
591}
592
593impl Location {
594 /// Encode the location (group, object) into the buffer.
595 pub fn encode(&self, buf: &mut impl BufMut) {
596 self.group.encode(buf);
597 self.object.encode(buf);
598 }
599
600 /// Decode a location from the buffer.
601 pub fn decode(buf: &mut impl Buf) -> Result<Self, crate::error::CodecError> {
602 let group = VarInt::decode(buf)?;
603 let object = VarInt::decode(buf)?;
604 Ok(Location { group, object })
605 }
606
607 /// Encode the location using the MoQT varint (drafts 17 and later).
608 pub fn encode_moqt<P: MoqtProfile>(&self, buf: &mut impl BufMut) {
609 self.group.encode_moqt::<P>(buf);
610 self.object.encode_moqt::<P>(buf);
611 }
612
613 /// Decode a location using the MoQT varint (drafts 17 and later).
614 pub fn decode_moqt<P: MoqtProfile>(
615 buf: &mut impl Buf,
616 ) -> Result<Self, crate::error::CodecError> {
617 let group = VarInt::decode_moqt::<P>(buf)?;
618 let object = VarInt::decode_moqt::<P>(buf)?;
619 Ok(Location { group, object })
620 }
621}
622
623impl ObjectStatus {
624 /// Every status draft-14 assigns, in ascending wire order.
625 ///
626 /// This is exactly the set [`ObjectStatus::from_u8`] accepts. Any other
627 /// value is one the draft does not assign.
628 pub const ALL: &[ObjectStatus] = &[
629 ObjectStatus::Normal,
630 ObjectStatus::DoesNotExist,
631 ObjectStatus::EndOfGroup,
632 ObjectStatus::EndOfTrack,
633 ];
634
635 /// Convert a raw byte to an `ObjectStatus`, or `None` if draft-14 does not
636 /// assign that value.
637 pub fn from_u8(v: u8) -> Option<Self> {
638 match v {
639 0x0 => Some(ObjectStatus::Normal),
640 0x1 => Some(ObjectStatus::DoesNotExist),
641 0x3 => Some(ObjectStatus::EndOfGroup),
642 0x4 => Some(ObjectStatus::EndOfTrack),
643 _ => None,
644 }
645 }
646
647 /// Return the wire value.
648 pub fn as_u8(self) -> u8 {
649 self as u8
650 }
651}
652
653impl GroupOrder {
654 /// Convert a raw byte to a `GroupOrder`, if valid.
655 pub fn from_u8(v: u8) -> Option<Self> {
656 match v {
657 0x0 => Some(GroupOrder::Publisher),
658 0x1 => Some(GroupOrder::Ascending),
659 0x2 => Some(GroupOrder::Descending),
660 _ => None,
661 }
662 }
663}
664
665impl ForwardingPreference {
666 /// Convert a raw byte to a `ForwardingPreference`, if valid.
667 pub fn from_u8(v: u8) -> Option<Self> {
668 match v {
669 0x0 => Some(ForwardingPreference::Object),
670 0x1 => Some(ForwardingPreference::Datagram),
671 _ => None,
672 }
673 }
674}
675
676impl FilterType {
677 /// Convert a raw byte to a `FilterType`, if valid.
678 pub fn from_u8(v: u8) -> Option<Self> {
679 Self::from_u64(v as u64)
680 }
681
682 /// Convert a raw u64 to a `FilterType`, if valid.
683 pub fn from_u64(v: u64) -> Option<Self> {
684 match v {
685 0x1 => Some(FilterType::NextGroupStart),
686 0x2 => Some(FilterType::LargestObject),
687 0x3 => Some(FilterType::AbsoluteStart),
688 0x4 => Some(FilterType::AbsoluteRange),
689 _ => None,
690 }
691 }
692}