Skip to main content

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}