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(¶meter_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 ¶meter.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}