Skip to main content

moqtap_codec/
range_filter.rs

1//! The Range Filters drafts 19 through 23 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 through 23 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    /// Draft-19 Section 10.2.13 and draft-19 Section 10.2.14 say the same of
182    /// both: the filter selects a range "for a required Object Property Type
183    /// which MUST be even, i.e. a single integer value (see Figure 2),
184    /// otherwise the endpoint MUST reject this with REQUEST_ERROR with error
185    /// code INVALID_FILTER".
186    ///
187    /// The parity is the Key-Value-Pair convention doing the work: an even Type
188    /// carries a bare varint and an odd one carries length-prefixed bytes, so an
189    /// odd Property Type is a property whose value is not a number and a range
190    /// over it has nothing to compare.
191    #[error("property filter names property type {0}, which is not an integer-valued one")]
192    PropertyTypeIsNotAnInteger(u64),
193}
194
195/// The value ran out before `detail`.
196const NO_SET_ID: &str = "it ends before the SetID";
197const NO_PROPERTY_TYPE: &str = "it ends before the Property Type this filter type carries";
198
199impl RangeFilter {
200    /// Read a Range Filter from the bytes of its parameter value.
201    ///
202    /// `bytes` is the value after the Key-Value-Pair's Length has been consumed,
203    /// which is what a decoded parameter holds — the Length in Section 5.1.3's
204    /// figure is that same prefix and not a second one inside the value.
205    /// An empty value decodes to a filter with no ranges rather than an error:
206    /// "In REQUEST_UPDATE, Length can be 0 to remove a filter parameter or
207    /// non-zero to replace that entire filter parameter including all sets and
208    /// Property Types." The three-field filters can express that in zero bytes
209    /// only if the SetID is also absent, so an empty value is taken as the
210    /// removal and anything shorter than its own prefix is not.
211    pub fn decode_moqt<P: MoqtProfile>(
212        parameter_type: u64,
213        bytes: &[u8],
214    ) -> Result<Self, RangeFilterError> {
215        let filter = Self::decode_moqt_structure::<P>(parameter_type, bytes)?;
216        filter.check_its_own_types()?;
217        Ok(filter)
218    }
219
220    /// Read a Range Filter without holding it to the two rules about its own
221    /// contents that [`check_its_own_types`](Self::check_its_own_types) states.
222    ///
223    /// The bytes still have to be a Range Filter and still have to parse: a
224    /// truncated value, a missing SetID and a delta that overflows are all
225    /// refused here exactly as they are by [`decode_moqt`](Self::decode_moqt).
226    /// What is not refused is a filter that decoded cleanly and then names a
227    /// Publisher Priority above 255 or an odd Property Type.
228    ///
229    /// # When to reach for this instead
230    ///
231    /// A decoder must refuse those two, because they are answered with
232    /// REQUEST_ERROR and passing them on would let an application act on a
233    /// range the peer is not allowed to have asked for. A **renderer** must
234    /// not: it is describing a message that has already been decoded, and a
235    /// filter that broke a content rule is exactly the one whose fields a
236    /// reader most needs to see. Refusing there reduces the rendering to
237    /// opaque bytes and hides the offending value inside them.
238    ///
239    /// So the split is by what the caller does with the answer, not by how
240    /// much checking it wants: decode with [`decode_moqt`](Self::decode_moqt),
241    /// render with this and then call
242    /// [`check_its_own_types`](Self::check_its_own_types) to say what is wrong
243    /// alongside the fields rather than instead of them.
244    pub fn decode_moqt_structure<P: MoqtProfile>(
245        parameter_type: u64,
246        bytes: &[u8],
247    ) -> Result<Self, RangeFilterError> {
248        if !is_range_filter(parameter_type) {
249            return Err(RangeFilterError::NotARangeFilter(parameter_type));
250        }
251        if bytes.is_empty() {
252            return Ok(RangeFilter {
253                parameter_type,
254                set_id: 0,
255                property_type: None,
256                ranges: Vec::new(),
257            });
258        }
259
260        let mut buf = bytes;
261        if buf.remaining() < 1 {
262            return Err(RangeFilterError::Malformed { detail: NO_SET_ID });
263        }
264        let set_id = buf.get_u8();
265
266        let property_type = if carries_a_property_type(parameter_type) {
267            Some(
268                VarInt::decode_moqt::<P>(&mut buf)
269                    .map_err(|_| RangeFilterError::Malformed { detail: NO_PROPERTY_TYPE })?
270                    .into_inner(),
271            )
272        } else {
273            None
274        };
275
276        // Two baselines, alternating. A Start counts from the previous Range's
277        // End and an End counts from the Start beside it, so the running value
278        // is reset by each field rather than carried across the pair.
279        let mut ranges = Vec::new();
280        let mut previous_end: u64 = 0;
281        while buf.has_remaining() {
282            let delta = VarInt::decode_moqt::<P>(&mut buf)
283                .map_err(|_| RangeFilterError::Malformed { detail: "a Range's Start is short" })?
284                .into_inner();
285            let start = previous_end
286                .checked_add(delta)
287                .ok_or(RangeFilterError::DeltaOverflow { base: previous_end, delta })?;
288
289            if !buf.has_remaining() {
290                // The final End is the only one that may be left off, and
291                // leaving it off means the range has no end.
292                ranges.push(FilterRange { start, end: None });
293                break;
294            }
295
296            let delta = VarInt::decode_moqt::<P>(&mut buf)
297                .map_err(|_| RangeFilterError::Malformed { detail: "a Range's End is short" })?
298                .into_inner();
299            let end = start
300                .checked_add(delta)
301                .ok_or(RangeFilterError::DeltaOverflow { base: start, delta })?;
302            ranges.push(FilterRange { start, end: Some(end) });
303            previous_end = end;
304        }
305
306        Ok(RangeFilter { parameter_type, set_id, property_type, ranges })
307    }
308
309    /// The two rules a Range Filter can break once it has decoded cleanly.
310    ///
311    /// Both are answered with the same REQUEST_ERROR as the delta rule, so
312    /// [`decode_moqt`](Self::decode_moqt) applies them for you and a filter
313    /// that reaches an application through it has already been held to
314    /// everything draft-19 Section 5.1.3 and draft-19 Section 10.2.12 through
315    /// draft-19 Section 10.2.14 state about its own contents. What is left for
316    /// the session to decide is the ceiling and the repeats, which need more
317    /// than one parameter to see.
318    ///
319    /// It is public so that a caller which decoded with
320    /// [`decode_moqt_structure`](Self::decode_moqt_structure) can still ask the
321    /// question — and, having the filter in hand, report the answer beside the
322    /// fields rather than in place of them.
323    pub fn check_its_own_types(&self) -> Result<(), RangeFilterError> {
324        if let Some(property_type) = self.property_type {
325            if !property_type.is_multiple_of(2) {
326                return Err(RangeFilterError::PropertyTypeIsNotAnInteger(property_type));
327            }
328        }
329        if self.parameter_type == PRIORITY_FILTER_PARAMETER {
330            for range in &self.ranges {
331                for value in [Some(range.start), range.end].into_iter().flatten() {
332                    if value > 255 {
333                        return Err(RangeFilterError::PriorityAboveTheField(value));
334                    }
335                }
336            }
337        }
338        Ok(())
339    }
340
341    /// Write this filter as a parameter value.
342    ///
343    /// Refuses what the decoder refuses, and one thing more: a range whose End is
344    /// below its Start, and an unbounded range anywhere but last. Both encode
345    /// perfectly well and neither reads back as what was written — a backwards
346    /// End wraps its delta into a nine-byte integer that resolves to an
347    /// unrelated value, and an unbounded range in the middle silently pairs its
348    /// successor's Start as its own End.
349    pub fn encode_moqt<P: MoqtProfile>(
350        &self,
351        buf: &mut impl BufMut,
352    ) -> Result<(), RangeFilterError> {
353        if !is_range_filter(self.parameter_type) {
354            return Err(RangeFilterError::NotARangeFilter(self.parameter_type));
355        }
356        match (carries_a_property_type(self.parameter_type), self.property_type) {
357            (true, None) => {
358                return Err(RangeFilterError::Malformed {
359                    detail: "this filter type carries a Property Type and none was given",
360                })
361            }
362            (false, Some(_)) => {
363                return Err(RangeFilterError::Malformed {
364                    detail: "this filter type carries no Property Type and one was given",
365                })
366            }
367            _ => {}
368        }
369        self.check_its_own_types()?;
370
371        let mut out = Vec::new();
372        out.push(self.set_id);
373        if let Some(property_type) = self.property_type {
374            VarInt::from_u64_moqt(property_type).encode_moqt::<P>(&mut out);
375        }
376
377        let mut previous_end: u64 = 0;
378        for (index, range) in self.ranges.iter().enumerate() {
379            let start_delta =
380                range.start.checked_sub(previous_end).ok_or(RangeFilterError::Malformed {
381                    detail: "the Ranges are not in ascending order",
382                })?;
383            VarInt::from_u64_moqt(start_delta).encode_moqt::<P>(&mut out);
384
385            match range.end {
386                Some(end) => {
387                    let end_delta =
388                        end.checked_sub(range.start).ok_or(RangeFilterError::Malformed {
389                            detail: "a Range ends before it starts",
390                        })?;
391                    VarInt::from_u64_moqt(end_delta).encode_moqt::<P>(&mut out);
392                    previous_end = end;
393                }
394                None if index + 1 == self.ranges.len() => {}
395                None => {
396                    return Err(RangeFilterError::Malformed {
397                        detail: "only the final Range may be left open",
398                    })
399                }
400            }
401        }
402
403        buf.put_slice(&out);
404        Ok(())
405    }
406
407    /// Whether `value` passes this filter.
408    ///
409    /// A filter with no ranges passes nothing, which is what a filter that lists
410    /// no acceptable values says. The removal form of a REQUEST_UPDATE is the
411    /// same shape on the wire and is not the same statement, so a caller acting
412    /// on an update reads the removal from the parameter's zero Length before it
413    /// asks anything to pass.
414    pub fn passes(&self, value: u64) -> bool {
415        self.ranges.iter().any(|range| range.contains(value))
416    }
417
418    /// This filter's key for the rule about repeats.
419    pub fn key(&self) -> RangeFilterKey {
420        (self.parameter_type, self.set_id, self.property_type)
421    }
422}
423
424/// Read every Range Filter in a decoded parameter list, in the order they arrive.
425///
426/// Parameters that are not Range Filters are skipped, so this can be handed the
427/// whole list a message carries.
428pub fn decode_all_moqt<P: MoqtProfile>(
429    parameters: &[KeyValuePair],
430) -> Result<Vec<RangeFilter>, RangeFilterError> {
431    let mut filters = Vec::new();
432    for parameter in parameters {
433        let parameter_type = parameter.key.into_inner();
434        if !is_range_filter(parameter_type) {
435            continue;
436        }
437        let bytes = match &parameter.value {
438            KvpValue::Bytes(bytes) => bytes.as_slice(),
439            // Unreachable from draft-19's decoder, which picks the shape from
440            // the parameter table and finds all five length-prefixed. A caller
441            // that built the pair in memory can still get here.
442            KvpValue::Varint(_) => {
443                return Err(RangeFilterError::Malformed {
444                    detail: "its value is a bare varint where the type defines a filter",
445                })
446            }
447        };
448        filters.push(RangeFilter::decode_moqt::<P>(parameter_type, bytes)?);
449    }
450    Ok(filters)
451}
452
453/// The total number of Ranges across `filters`.
454///
455/// This is the count MAX_FILTER_RANGES bounds: "Range Filters are only allowed
456/// if the setup option MAX_FILTER_RANGES is non-zero, which limits the total
457/// number of Ranges allowed in all Range Filter parameters for a given
458/// subscription or fetch." Across all of them, so a peer cannot spend the budget
459/// a parameter at a time.
460///
461/// The ceiling itself is not applied here. It comes from a Setup Option, it is
462/// per subscription rather than per message, and exceeding it is answered with a
463/// REQUEST_ERROR — three reasons it belongs to whatever holds the session.
464pub fn total_ranges(filters: &[RangeFilter]) -> usize {
465    filters.iter().map(|filter| filter.ranges.len()).sum()
466}
467
468/// The first key that appears twice, if any.
469///
470/// "If the same combination of Parameter Type, SetID, and Property Type (only in
471/// the Track and Object Property Filters) repeat in any message, an endpoint
472/// MUST reject this with REQUEST_ERROR with error code INVALID_FILTER."
473///
474/// Repeats of a Range Filter type are otherwise expected — "The Track Property
475/// filter parameter MAY appear multiple times in a SUBSCRIBE_TRACKS message" —
476/// so the type alone is not the key and a check written against it would refuse
477/// what the same section permits two paragraphs earlier.
478pub fn first_repeated_key(filters: &[RangeFilter]) -> Option<RangeFilterKey> {
479    let mut seen: Vec<RangeFilterKey> = Vec::with_capacity(filters.len());
480    for filter in filters {
481        let key = filter.key();
482        if seen.contains(&key) {
483            return Some(key);
484        }
485        seen.push(key);
486    }
487    None
488}