Skip to main content

moqtap_codec/
range_filter.rs

1//! The Range Filters drafts 19 and 20 carry in five parameters.
2//!
3//! Section 5.1.3: "Range Filters are parameters in SUBSCRIBE, FETCH, or
4//! SUBSCRIBE_TRACKS that tell a publisher to filter tracks (via TRACK PROPERTY
5//! FILTER) and objects according to subscriber-provided criteria. Range filters
6//! are specified as ranges of integer values in Track and Object Properties and
7//! other Object header fields (Subgroup ID, Object ID, and Publisher Priority).
8//! There are five Range Filter parameter types, 0x25-0x29, as shown below."
9//!
10//! Drafts 19 and 20 are the only drafts with them, and the five share one
11//! value shape:
12//!
13//! ```text
14//! SUBGROUP_FILTER        { Type=0x25, Length, [SetID], Range... }
15//! OBJECTID_FILTER        { Type=0x26, Length, [SetID], Range... }
16//! PRIORITY_FILTER        { Type=0x27, Length, [SetID], Range... }
17//! OBJECT_PROPERTY_FILTER { Type=0x28, Length, [SetID], [Property Type], Range... }
18//! TRACK_PROPERTY_FILTER  { Type=0x29, Length, [SetID], [Property Type], Range... }
19//! Range                  { Start, [End] }
20//! ```
21//!
22//! # Why this is a reader and not a check
23//!
24//! Every consequence Section 5.1.3 states is a **reply**, not a session close:
25//! "MUST be rejected with REQUEST_ERROR with error code INVALID_FILTER" for a
26//! delta that overruns, for a repeated filter key, and for more Ranges than
27//! MAX_FILTER_RANGES allows. A reply is something an endpoint sends, and sending
28//! it needs the request decoded — including the Request ID the reply names.
29//!
30//! So nothing here is wired into `decode_parameters`, and [`RangeFilterError`]
31//! deliberately does not convert into [`CodecError`](crate::error::CodecError).
32//! A refusal at decode time would turn a frame the endpoint owes an answer to
33//! into a frame it never saw, and the subscriber would wait for a REQUEST_ERROR
34//! that no longer has anything to be about. The five types are registered in
35//! draft-19's parameter table as length-prefixed values, which is what keeps
36//! them carried rather than refused; this module is what makes the bytes mean
37//! something.
38//!
39//! # The delta encoding, which is not the one used elsewhere
40//!
41//! "Start is delta encoded from the prior Range's End or from 0 for the first
42//! Range, and End is delta encoded from the current Range's Start." So the
43//! baseline alternates: a Start counts from the previous End, and an End counts
44//! from the Start beside it. The draft's own example is ranges 3-5 and 10-15,
45//! written as 3, 2, 5, 5 — and reading it with one running baseline instead of
46//! two produces 3-5 and 8-13, a range that is wrong and well formed.
47//!
48//! "The final End in a sequence of Ranges can be omitted to indicate no end", so
49//! a value holding an odd number of integers ends in an unbounded range. Only
50//! the last one may be omitted, which is what makes the pairing unambiguous.
51
52use bytes::{Buf, BufMut};
53
54use crate::kvp::{KeyValuePair, KvpValue};
55use crate::varint::{MoqtProfile, VarInt};
56
57/// SUBGROUP_FILTER, matching an Object's Subgroup ID.
58pub const SUBGROUP_FILTER_PARAMETER: u64 = 0x25;
59/// OBJECTID_FILTER, matching an Object's Object ID.
60pub const OBJECT_ID_FILTER_PARAMETER: u64 = 0x26;
61/// PRIORITY_FILTER, matching an Object's Publisher Priority.
62pub const PRIORITY_FILTER_PARAMETER: u64 = 0x27;
63/// OBJECT_PROPERTY_FILTER, matching the value of one Object Property.
64pub const OBJECT_PROPERTY_FILTER_PARAMETER: u64 = 0x28;
65/// TRACK_PROPERTY_FILTER, matching the value of one Track Property.
66pub const TRACK_PROPERTY_FILTER_PARAMETER: u64 = 0x29;
67
68/// Whether `parameter_type` is one of the five Range Filters.
69pub fn is_range_filter(parameter_type: u64) -> bool {
70    (SUBGROUP_FILTER_PARAMETER..=TRACK_PROPERTY_FILTER_PARAMETER).contains(&parameter_type)
71}
72
73/// Whether a Range Filter of this type carries a Property Type after its SetID.
74///
75/// The two property filters do and the three header-field filters do not: a
76/// filter on Subgroup ID, Object ID or Publisher Priority already knows which
77/// field it is about from its own parameter type, and a filter on a property has
78/// to name which property. Reading the prefix for the wrong three shifts every
79/// Range in the value by one integer.
80pub fn carries_a_property_type(parameter_type: u64) -> bool {
81    parameter_type == OBJECT_PROPERTY_FILTER_PARAMETER
82        || parameter_type == TRACK_PROPERTY_FILTER_PARAMETER
83}
84
85/// One inclusive range of values, with the deltas already resolved.
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub struct FilterRange {
88    /// The first value the range admits.
89    pub start: u64,
90    /// The last value the range admits, or `None` for a range with no end.
91    ///
92    /// Only the final Range in a filter may be unbounded, because the omission
93    /// is what the reader uses to tell a trailing Start from the next Range's.
94    pub end: Option<u64>,
95}
96
97impl FilterRange {
98    /// Whether `value` falls inside this range.
99    ///
100    /// Both ends are inclusive: Section 5.1.3 calls them "Start/End (vi64)
101    /// inclusive Range pairs", and the example spells 3-5 as a Start of 3 and an
102    /// End of 5 rather than as a half-open interval.
103    pub fn contains(&self, value: u64) -> bool {
104        value >= self.start && self.end.is_none_or(|end| value <= end)
105    }
106}
107
108/// A decoded Range Filter parameter value.
109#[derive(Debug, Clone, PartialEq, Eq)]
110pub struct RangeFilter {
111    /// Which of the five filters this is.
112    pub parameter_type: u64,
113    /// The set this filter belongs to.
114    /// "All filter parameters with the same SetID value are combined using
115    /// logical 'AND' operations, then all the resulting sets are combined using
116    /// logical 'OR' operations." So the SetID is not decoration: two filters
117    /// under one SetID both have to pass, and two filters under different
118    /// SetIDs each pass on their own.
119    pub set_id: u8,
120    /// The property this filter is about, on the two property filters.
121    ///
122    /// `None` on SUBGROUP_FILTER, OBJECTID_FILTER and PRIORITY_FILTER, whose
123    /// subject is fixed by the parameter type.
124    pub property_type: Option<u64>,
125    /// The ranges, with every delta resolved to an absolute value.
126    pub ranges: Vec<FilterRange>,
127}
128
129/// What the key of a Range Filter is, for the rule about repeats.
130///
131/// "If the same combination of Parameter Type, SetID, and Property Type (only in
132/// the Track and Object Property Filters) repeat in any message, an endpoint
133/// MUST reject this with REQUEST_ERROR with error code INVALID_FILTER."
134pub type RangeFilterKey = (u64, u8, Option<u64>);
135
136/// A Range Filter value this codec could not turn into ranges.
137///
138/// Kept out of [`CodecError`](crate::error::CodecError) on purpose, and the
139/// absence of a `From` impl is the mechanism: draft-19 answers every one of
140/// these with a REQUEST_ERROR carrying INVALID_FILTER, which is a message the
141/// endpoint sends after decoding the request, so none of them may become a
142/// decoder refusal. See this module's header.
143#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
144pub enum RangeFilterError {
145    /// A parameter type outside 0x25 through 0x29 was handed to the reader.
146    #[error("parameter type {0} is not a Range Filter")]
147    NotARangeFilter(u64),
148    /// The value ran out before a field the shape requires.
149    #[error("range filter value is malformed: {detail}")]
150    Malformed {
151        /// Which field was missing, for a log that says what was short.
152        detail: &'static str,
153    },
154    /// A delta resolved past the end of the 64-bit space.
155    ///
156    /// "Any delta encoding that results in a value that exceeds 2^64-1 MUST be
157    /// rejected with REQUEST_ERROR with error code INVALID_FILTER." The sum is
158    /// checked rather than allowed to wrap: a wrapped Start is a small number,
159    /// and a filter that reads as 0-5 when its sender wrote something above the
160    /// end of the space passes Objects it was written to exclude.
161    #[error("range filter delta {delta} past {base} runs off the end of the 64-bit space")]
162    DeltaOverflow {
163        /// The value the delta was counted from.
164        base: u64,
165        /// The delta as it arrived.
166        delta: u64,
167    },
168    /// A PRIORITY_FILTER range names a value the field cannot hold.
169    ///
170    /// Section 10.2.12: "If a decoded value exceeds 255, the endpoint MUST
171    /// reject this with REQUEST_ERROR with error code INVALID_FILTER since
172    /// Publisher Priority is an 8-bit field."
173    ///
174    /// "A decoded value", so the resolved Start or End rather than the delta
175    /// that spelled it — a filter of 200 to 300 has both deltas well inside the
176    /// byte and one endpoint outside it.
177    #[error("priority filter names {0}, which a Publisher Priority cannot hold")]
178    PriorityAboveTheField(u64),
179    /// A property filter names a Property Type that is not an integer-valued one.
180    ///
181    /// Sections 10.2.13 and 10.2.14 say the same of both: the filter selects a
182    /// range "for a required Object Property Type which MUST be even, i.e. a
183    /// single integer value (see Figure 2), otherwise the endpoint MUST reject
184    /// this with REQUEST_ERROR with error code INVALID_FILTER".
185    ///
186    /// The parity is the Key-Value-Pair convention doing the work: an even Type
187    /// carries a bare varint and an odd one carries length-prefixed bytes, so an
188    /// odd Property Type is a property whose value is not a number and a range
189    /// over it has nothing to compare.
190    #[error("property filter names property type {0}, which is not an integer-valued one")]
191    PropertyTypeIsNotAnInteger(u64),
192}
193
194/// The value ran out before `detail`.
195const NO_SET_ID: &str = "it ends before the SetID";
196const NO_PROPERTY_TYPE: &str = "it ends before the Property Type this filter type carries";
197
198impl RangeFilter {
199    /// Read a Range Filter from the bytes of its parameter value.
200    ///
201    /// `bytes` is the value after the Key-Value-Pair's Length has been consumed,
202    /// which is what a decoded parameter holds — the Length in Section 5.1.3's
203    /// figure is that same prefix and not a second one inside the value.
204    /// An empty value decodes to a filter with no ranges rather than an error:
205    /// "In REQUEST_UPDATE, Length can be 0 to remove a filter parameter or
206    /// non-zero to replace that entire filter parameter including all sets and
207    /// Property Types." The three-field filters can express that in zero bytes
208    /// only if the SetID is also absent, so an empty value is taken as the
209    /// removal and anything shorter than its own prefix is not.
210    pub fn decode_moqt<P: MoqtProfile>(
211        parameter_type: u64,
212        bytes: &[u8],
213    ) -> Result<Self, RangeFilterError> {
214        let filter = Self::decode_moqt_structure::<P>(parameter_type, bytes)?;
215        filter.check_its_own_types()?;
216        Ok(filter)
217    }
218
219    /// Read a Range Filter without holding it to the two rules about its own
220    /// contents that [`check_its_own_types`](Self::check_its_own_types) states.
221    ///
222    /// The bytes still have to be a Range Filter and still have to parse: a
223    /// truncated value, a missing SetID and a delta that overflows are all
224    /// refused here exactly as they are by [`decode_moqt`](Self::decode_moqt).
225    /// What is not refused is a filter that decoded cleanly and then names a
226    /// Publisher Priority above 255 or an odd Property Type.
227    ///
228    /// # When to reach for this instead
229    ///
230    /// A decoder must refuse those two, because they are answered with
231    /// REQUEST_ERROR and passing them on would let an application act on a
232    /// range the peer is not allowed to have asked for. A **renderer** must
233    /// not: it is describing a message that has already been decoded, and a
234    /// filter that broke a content rule is exactly the one whose fields a
235    /// reader most needs to see. Refusing there degrades the rendering to
236    /// opaque bytes and hides the offending value inside them.
237    ///
238    /// So the split is by what the caller does with the answer, not by how
239    /// much checking it wants: decode with [`decode_moqt`](Self::decode_moqt),
240    /// render with this and then call
241    /// [`check_its_own_types`](Self::check_its_own_types) to say what is wrong
242    /// alongside the fields rather than instead of them.
243    pub fn decode_moqt_structure<P: MoqtProfile>(
244        parameter_type: u64,
245        bytes: &[u8],
246    ) -> Result<Self, RangeFilterError> {
247        if !is_range_filter(parameter_type) {
248            return Err(RangeFilterError::NotARangeFilter(parameter_type));
249        }
250        if bytes.is_empty() {
251            return Ok(RangeFilter {
252                parameter_type,
253                set_id: 0,
254                property_type: None,
255                ranges: Vec::new(),
256            });
257        }
258
259        let mut buf = bytes;
260        if buf.remaining() < 1 {
261            return Err(RangeFilterError::Malformed { detail: NO_SET_ID });
262        }
263        let set_id = buf.get_u8();
264
265        let property_type = if carries_a_property_type(parameter_type) {
266            Some(
267                VarInt::decode_moqt::<P>(&mut buf)
268                    .map_err(|_| RangeFilterError::Malformed { detail: NO_PROPERTY_TYPE })?
269                    .into_inner(),
270            )
271        } else {
272            None
273        };
274
275        // Two baselines, alternating. A Start counts from the previous Range's
276        // End and an End counts from the Start beside it, so the running value
277        // is reset by each field rather than carried across the pair.
278        let mut ranges = Vec::new();
279        let mut previous_end: u64 = 0;
280        while buf.has_remaining() {
281            let delta = VarInt::decode_moqt::<P>(&mut buf)
282                .map_err(|_| RangeFilterError::Malformed { detail: "a Range's Start is short" })?
283                .into_inner();
284            let start = previous_end
285                .checked_add(delta)
286                .ok_or(RangeFilterError::DeltaOverflow { base: previous_end, delta })?;
287
288            if !buf.has_remaining() {
289                // The final End is the only one that may be left off, and
290                // leaving it off means the range has no end.
291                ranges.push(FilterRange { start, end: None });
292                break;
293            }
294
295            let delta = VarInt::decode_moqt::<P>(&mut buf)
296                .map_err(|_| RangeFilterError::Malformed { detail: "a Range's End is short" })?
297                .into_inner();
298            let end = start
299                .checked_add(delta)
300                .ok_or(RangeFilterError::DeltaOverflow { base: start, delta })?;
301            ranges.push(FilterRange { start, end: Some(end) });
302            previous_end = end;
303        }
304
305        Ok(RangeFilter { parameter_type, set_id, property_type, ranges })
306    }
307
308    /// The two rules a Range Filter can break once it has decoded cleanly.
309    ///
310    /// Both are answered with the same REQUEST_ERROR as the delta rule, so
311    /// [`decode_moqt`](Self::decode_moqt) applies them for you and a filter
312    /// that reaches an application through it has already been held to
313    /// everything Section 5.1.3 and Sections 10.2.12 through 10.2.14 state
314    /// about its own contents. What is left for the session to decide is the
315    /// ceiling and the repeats, which need more than one parameter to see.
316    ///
317    /// It is public so that a caller which decoded with
318    /// [`decode_moqt_structure`](Self::decode_moqt_structure) can still ask the
319    /// question — and, having the filter in hand, report the answer beside the
320    /// fields rather than in place of them.
321    pub fn check_its_own_types(&self) -> Result<(), RangeFilterError> {
322        if let Some(property_type) = self.property_type {
323            if !property_type.is_multiple_of(2) {
324                return Err(RangeFilterError::PropertyTypeIsNotAnInteger(property_type));
325            }
326        }
327        if self.parameter_type == PRIORITY_FILTER_PARAMETER {
328            for range in &self.ranges {
329                for value in [Some(range.start), range.end].into_iter().flatten() {
330                    if value > 255 {
331                        return Err(RangeFilterError::PriorityAboveTheField(value));
332                    }
333                }
334            }
335        }
336        Ok(())
337    }
338
339    /// Write this filter as a parameter value.
340    ///
341    /// Refuses what the decoder refuses, and one thing more: a range whose End is
342    /// below its Start, and an unbounded range anywhere but last. Both encode
343    /// perfectly well and neither reads back as what was written — a backwards
344    /// End wraps its delta into a nine-byte integer that resolves to an
345    /// unrelated value, and an unbounded range in the middle silently pairs its
346    /// successor's Start as its own End.
347    pub fn encode_moqt<P: MoqtProfile>(
348        &self,
349        buf: &mut impl BufMut,
350    ) -> Result<(), RangeFilterError> {
351        if !is_range_filter(self.parameter_type) {
352            return Err(RangeFilterError::NotARangeFilter(self.parameter_type));
353        }
354        match (carries_a_property_type(self.parameter_type), self.property_type) {
355            (true, None) => {
356                return Err(RangeFilterError::Malformed {
357                    detail: "this filter type carries a Property Type and none was given",
358                })
359            }
360            (false, Some(_)) => {
361                return Err(RangeFilterError::Malformed {
362                    detail: "this filter type carries no Property Type and one was given",
363                })
364            }
365            _ => {}
366        }
367        self.check_its_own_types()?;
368
369        let mut out = Vec::new();
370        out.push(self.set_id);
371        if let Some(property_type) = self.property_type {
372            VarInt::from_u64_moqt(property_type).encode_moqt::<P>(&mut out);
373        }
374
375        let mut previous_end: u64 = 0;
376        for (index, range) in self.ranges.iter().enumerate() {
377            let start_delta =
378                range.start.checked_sub(previous_end).ok_or(RangeFilterError::Malformed {
379                    detail: "the Ranges are not in ascending order",
380                })?;
381            VarInt::from_u64_moqt(start_delta).encode_moqt::<P>(&mut out);
382
383            match range.end {
384                Some(end) => {
385                    let end_delta =
386                        end.checked_sub(range.start).ok_or(RangeFilterError::Malformed {
387                            detail: "a Range ends before it starts",
388                        })?;
389                    VarInt::from_u64_moqt(end_delta).encode_moqt::<P>(&mut out);
390                    previous_end = end;
391                }
392                None if index + 1 == self.ranges.len() => {}
393                None => {
394                    return Err(RangeFilterError::Malformed {
395                        detail: "only the final Range may be left open",
396                    })
397                }
398            }
399        }
400
401        buf.put_slice(&out);
402        Ok(())
403    }
404
405    /// Whether `value` passes this filter.
406    ///
407    /// A filter with no ranges passes nothing, which is what a filter that lists
408    /// no acceptable values says. The removal form of a REQUEST_UPDATE is the
409    /// same shape on the wire and is not the same statement, so a caller acting
410    /// on an update reads the removal from the parameter's zero Length before it
411    /// asks anything to pass.
412    pub fn passes(&self, value: u64) -> bool {
413        self.ranges.iter().any(|range| range.contains(value))
414    }
415
416    /// This filter's key for the rule about repeats.
417    pub fn key(&self) -> RangeFilterKey {
418        (self.parameter_type, self.set_id, self.property_type)
419    }
420}
421
422/// Read every Range Filter in a decoded parameter list, in the order they arrive.
423///
424/// Parameters that are not Range Filters are skipped, so this can be handed the
425/// whole list a message carries.
426pub fn decode_all_moqt<P: MoqtProfile>(
427    parameters: &[KeyValuePair],
428) -> Result<Vec<RangeFilter>, RangeFilterError> {
429    let mut filters = Vec::new();
430    for parameter in parameters {
431        let parameter_type = parameter.key.into_inner();
432        if !is_range_filter(parameter_type) {
433            continue;
434        }
435        let bytes = match &parameter.value {
436            KvpValue::Bytes(bytes) => bytes.as_slice(),
437            // Unreachable from draft-19's decoder, which picks the shape from
438            // the parameter table and finds all five length-prefixed. A caller
439            // that built the pair in memory can still get here.
440            KvpValue::Varint(_) => {
441                return Err(RangeFilterError::Malformed {
442                    detail: "its value is a bare varint where the type defines a filter",
443                })
444            }
445        };
446        filters.push(RangeFilter::decode_moqt::<P>(parameter_type, bytes)?);
447    }
448    Ok(filters)
449}
450
451/// The total number of Ranges across `filters`.
452///
453/// This is the count MAX_FILTER_RANGES bounds: "Range Filters are only allowed
454/// if the setup option MAX_FILTER_RANGES is non-zero, which limits the total
455/// number of Ranges allowed in all Range Filter parameters for a given
456/// subscription or fetch." Across all of them, so a peer cannot spend the budget
457/// a parameter at a time.
458///
459/// The ceiling itself is not applied here. It comes from a Setup Option, it is
460/// per subscription rather than per message, and exceeding it is answered with a
461/// REQUEST_ERROR — three reasons it belongs to whatever holds the session.
462pub fn total_ranges(filters: &[RangeFilter]) -> usize {
463    filters.iter().map(|filter| filter.ranges.len()).sum()
464}
465
466/// The first key that appears twice, if any.
467///
468/// "If the same combination of Parameter Type, SetID, and Property Type (only in
469/// the Track and Object Property Filters) repeat in any message, an endpoint
470/// MUST reject this with REQUEST_ERROR with error code INVALID_FILTER."
471///
472/// Repeats of a Range Filter type are otherwise expected — "The Track Property
473/// filter parameter MAY appear multiple times in a SUBSCRIBE_TRACKS message" —
474/// so the type alone is not the key and a check written against it would refuse
475/// what the same section permits two paragraphs earlier.
476pub fn first_repeated_key(filters: &[RangeFilter]) -> Option<RangeFilterKey> {
477    let mut seen: Vec<RangeFilterKey> = Vec::with_capacity(filters.len());
478    for filter in filters {
479        let key = filter.key();
480        if seen.contains(&key) {
481            return Some(key);
482        }
483        seen.push(key);
484    }
485    None
486}