Skip to main content

moqtap_codec/
range_filter.rs

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