moqtap_client/draft22/fill.rs
1//! Location filters and fill fetch streams, the two things draft-22 asks a
2//! subscriber to build byte by byte.
3//!
4//! The range of a FETCH travels in the `LOCATION_FILTER` message parameter
5//! (Section 9.20.9), and `FILL_PARAMETERS` (Section 9.20.15) is a parameter
6//! whose *presence* on a SUBSCRIBE or a REQUEST_UPDATE asks the publisher to
7//! open a fill fetch stream, and whose value is a nested parameter block that
8//! overrides the subscription's own settings for that fill alone.
9//!
10//! Both are parameter values a caller would otherwise have to construct by
11//! hand, which is why they are here: neither carries anything a receiver could
12//! resynchronise on, so getting either wrong desynchronises the whole parameter
13//! list rather than producing a recognisable error. A `LOCATION_FILTER` has no
14//! length at all — its `Location Filter Type` is the only thing that says
15//! where it ends — and `FILL_PARAMETERS` is the place draft-22 is least
16//! explicit.
17//!
18//! # What this module decides, and where the draft is silent
19//!
20//! * A `FILL_PARAMETERS` value **begins with a `Number of Parameters`
21//! count**, and its `Type Delta` chain restarts at 0. See
22//! [`FillParameters`].
23//! * A fill fetch stream with no `GROUP_ORDER` anywhere is read **Ascending**.
24//! See [`group_order`].
25//!
26//! Every value this module builds is handed to the codec's own decoder before
27//! it is returned, so a disagreement between the two is an error here rather
28//! than a frame on the wire.
29//!
30//! # Ranges are inclusive
31//!
32//! Draft-19's fetch end was "the last Object, plus 1; or 0 to indicate the
33//! entire Group". Draft-22 Section 3.3.1 says a Location filter "specifies an
34//! inclusive range of Locations", Section 3.2 that a FETCH requests Objects
35//! "between a Start Location and an End Location, inclusive", and neither
36//! draft-19 convention survives. **Nothing in this module adds or subtracts one from
37//! an end location**, and a caller porting draft-19 arithmetic forward fetches
38//! one object too many.
39
40use moqtap_codec::draft22::data_stream::GroupOrder;
41use moqtap_codec::draft22::message::{
42 decode_fill_parameters, decode_location_filter, location_filter_types, FILL_PARAMETERS,
43 LOCATION_FILTER,
44};
45use moqtap_codec::kvp::{KeyValuePair, KvpValue};
46use moqtap_codec::varint::{Moqt18 as Wire, VarInt};
47
48/// `GROUP_ORDER`, Parameter Type 0x22 (Section 9.20.8).
49///
50/// Spelled here rather than imported because the codec's draft-22 message
51/// module does not export it, and because this module needs the same number in
52/// three places: Table 7's allow-list, the uint8 shape test, and [`group_order`].
53pub const GROUP_ORDER: u64 = 0x22;
54
55/// The `GROUP_ORDER` value Section 9.20.8 assigns to Ascending.
56const GROUP_ORDER_ASCENDING: u64 = 0x1;
57
58/// The `GROUP_ORDER` value Section 9.20.8 assigns to Descending.
59const GROUP_ORDER_DESCENDING: u64 = 0x2;
60
61/// Errors from building a `LOCATION_FILTER` or a `FILL_PARAMETERS` value.
62#[derive(Debug, thiserror::Error, PartialEq, Eq)]
63pub enum FillError {
64 /// `StartGroup + EndGroupDelta` left the number space.
65 ///
66 /// Section 9.20.9: "If StartGroup + EndGroupDelta exceeds 2^64 - 1, the
67 /// endpoint MUST close the session with a PROTOCOL_VIOLATION." A value the
68 /// peer must close the session over is not a value this endpoint has a way
69 /// of sending, so it is refused here instead.
70 #[error("start group {start_group} plus end group delta {delta} exceeds 2^64 - 1")]
71 EndGroupOverflow {
72 /// The start group the filter named.
73 start_group: u64,
74 /// The delta added to it.
75 delta: u64,
76 },
77 /// A parameter type Table 7 does not list was put inside `FILL_PARAMETERS`.
78 ///
79 /// Section 9.20.15: "An endpoint that receives a parameter inside
80 /// FILL_PARAMETERS that is not listed above MUST close the session with
81 /// PROTOCOL_VIOLATION."
82 #[error("parameter type {0:#x} may not appear inside FILL_PARAMETERS")]
83 NotAFillParameter(u64),
84 /// The same parameter type was put inside `FILL_PARAMETERS` twice where its
85 /// own definition does not allow repeats.
86 #[error("parameter type {0:#x} appears twice inside FILL_PARAMETERS")]
87 RepeatedFillParameter(u64),
88 /// A value whose shape does not match the type it was filed under.
89 #[error(
90 "the value under parameter type {key:#x} is not the shape that type defines: {detail}"
91 )]
92 MalformedValue {
93 /// The parameter type.
94 key: u64,
95 /// What is wrong with the value.
96 detail: &'static str,
97 },
98 /// The value this module built is one the codec's own decoder refuses.
99 ///
100 /// Unreachable unless the encoder here and the decoder in
101 /// `moqtap-codec` have drifted apart, which is what the check exists to
102 /// catch: a value that goes out and comes back as a session close is worse
103 /// than one that never goes out.
104 #[error("the encoded value does not decode: {0}")]
105 NotRoundTrippable(String),
106}
107
108/// The parameter types draft-22 Section 9.20.15, Table 7 permits inside a
109/// `FILL_PARAMETERS` value, in ascending order.
110///
111/// `TRACK_PROPERTY_FILTER` (0x29) is deliberately absent: it selects tracks,
112/// and a fill applies to one already-selected track. The same list is enforced
113/// on the decode side by `moqtap_codec::draft22::message::decode_fill_parameters`,
114/// which is what the round-trip check at the end of
115/// [`FillParameters::parameter`] leans on.
116const FILL_PARAMETERS_ALLOWED: &[u64] = &[0x0A, 0x20, 0x21, 0x22, 0x25, 0x26, 0x27, 0x28];
117
118/// The five Range Filters, of which four may be nested. Only these may repeat.
119///
120/// Section 3.3.2 lets the Range Filters "appear multiple times", and Section
121/// 9.20 forbids a repeat of anything else. A repeat is encoded as a `Type
122/// Delta` of 0, which is the only encoding a second instance of an ascending
123/// chain has.
124fn may_repeat(key: u64) -> bool {
125 (0x25..=0x29).contains(&key)
126}
127
128/// Whether a Table 7 parameter's value is a single raw byte rather than a
129/// varint or a length-prefixed block.
130///
131/// `SUBSCRIBER_PRIORITY` (0x20, Section 9.20.7) and `GROUP_ORDER` (0x22,
132/// Section 9.20.8) are the two uint8s that may be nested.
133fn is_uint8(key: u64) -> bool {
134 key == 0x20 || key == GROUP_ORDER
135}
136
137/// Whether a Table 7 parameter's value is a bare varint.
138///
139/// `FILL_TIMEOUT` (0x0A, Section 9.20.5) is the only one. `LOCATION_FILTER`
140/// (0x21) also opens with a varint, but a varint alone is only its type, and
141/// accepting one here would write a bare `0x05` as Next Object and a bare `0x00`
142/// as no filter: the codec's own encoder refuses that value, so this one does
143/// too.
144fn is_bare_varint(key: u64) -> bool {
145 key == 0x0A
146}
147
148/// Whether a Table 7 parameter's value carries its own length prefix.
149///
150/// The four nestable Range Filters (0x25 through 0x28). `FILL_TIMEOUT` (0x0A)
151/// is the only bare varint in the table, and `LOCATION_FILTER` (0x21) is
152/// neither: its value is a `Location Filter Type` and the fields that type
153/// names, with no length ahead of them, nested or not.
154fn is_length_prefixed(key: u64) -> bool {
155 (0x25..=0x28).contains(&key)
156}
157
158/// A draft-22 `LOCATION_FILTER` (Parameter Type 0x21), Section 9.20.9.
159///
160/// ```text
161/// LOCATION_FILTER Parameter {
162/// Parameter Type (vi64) = 0x21,
163/// Location Filter Type (vi64),
164/// [StartGroup (vi64),]
165/// [StartObject (vi64),]
166/// [EndGroupDelta (vi64),]
167/// [EndObject (vi64),]
168/// }
169/// ```
170///
171/// # The shape is the type
172///
173/// Section 9.20.9: "The Location Filter Type dictates which optional
174/// variable-length integer fields follow, and how they are interpreted." Each
175/// constructor here is one of the six types Table 6 lists, and the type is
176/// written first, so a caller cannot build a type the draft does not define,
177/// cannot build a three-field filter by leaving a field out of a four-field
178/// one, and cannot confuse the two types that carry no fields at all — None
179/// and Next Object, which mean opposite things.
180///
181/// There is no length. The type is the only thing that says where the value
182/// ends, so a receiver that reads the type wrong reads every parameter after
183/// it wrong too; this is why the value is built here and handed back through
184/// the codec's own decoder before it is returned.
185///
186/// # Migrating a draft-19 filter
187///
188/// Draft-19's filter also opened with a type, and its numbers are **not**
189/// these. Draft-19's Filter Type 0x1 (Next Group Start) is
190/// [`LocationFilter::relative`] with `0`, type 0x01 here. Its 0x2 (Largest
191/// Object) is [`LocationFilter::next_object`], type 0x05 here — not 0x02,
192/// which is an absolute start. Its 0x3 and 0x4 (AbsoluteStart and
193/// AbsoluteRange) are [`LocationFilter::absolute_start`] and
194/// [`LocationFilter::range`], types 0x02 and 0x03 here. A draft-19 type number
195/// copied across as a number asks a different question.
196#[derive(Debug, Clone, PartialEq, Eq)]
197pub struct LocationFilter {
198 filter_type: u64,
199 fields: Vec<u64>,
200}
201
202impl LocationFilter {
203 /// Type 0x00: no filter at all, and no fields.
204 ///
205 /// Section 9.20.9: "If Location Filter Type is 0x00, no fields follow and
206 /// there is no Location Filter." In a REQUEST_UPDATE this is how a filter
207 /// already in force is taken away: Section 9.5 has "no generic mechanism to
208 /// remove a parameter from a request", and omitting `LOCATION_FILTER` from
209 /// a REQUEST_UPDATE leaves the value unchanged rather than clearing it.
210 pub fn none() -> Self {
211 Self { filter_type: location_filter_types::NONE, fields: Vec::new() }
212 }
213
214 /// Type 0x01: a start relative to the live edge, open-ended.
215 ///
216 /// Section 9.20.9: the start Location is `{Largest Object.Group + 1 -
217 /// StartGroup, 0}`, so `0` is the next group, `1` the current group, and
218 /// `N` is `N - 1` groups before the current one. Clamped at both ends of
219 /// the number space by the publisher rather than here, because resolving it
220 /// needs Largest Object.
221 pub fn relative(start_group: u64) -> Self {
222 Self { filter_type: location_filter_types::RELATIVE_START, fields: vec![start_group] }
223 }
224
225 /// Type 0x02: an absolute start, open-ended.
226 ///
227 /// Every start is absolute here, `{0, 0}` included: it is the first
228 /// Location of the track. The live edge is a type of its own,
229 /// [`LocationFilter::next_object`].
230 pub fn absolute_start(start_group: u64, start_object: u64) -> Self {
231 Self {
232 filter_type: location_filter_types::ABSOLUTE_START,
233 fields: vec![start_group, start_object],
234 }
235 }
236
237 /// Type 0x05: start at the Next Object, open-ended, with no fields.
238 ///
239 /// Section 9.20.9: "If Location Filter Type is 0x05, no fields follow and
240 /// it specifies the Next Object." Section 3.4 names this filter as half of
241 /// the recipe for exactly-once delivery on a subscription with a fill: a
242 /// Next Object subscription filter paired with an open-ended fill range,
243 /// which the publisher ends at Largest Object.
244 pub fn next_object() -> Self {
245 Self { filter_type: location_filter_types::NEXT_OBJECT, fields: Vec::new() }
246 }
247
248 /// Type 0x03: an absolute start and an end group, covering **all** objects
249 /// in the end group.
250 ///
251 /// Section 9.20.9: "EndGroupDelta is delta encoded from StartGroup, but both
252 /// the start and end groups are absolute, not relative to Largest Object."
253 /// So the end group is `start_group + end_group_delta`, and a delta of 0
254 /// ends in the group it started in.
255 ///
256 /// # Errors
257 ///
258 /// [`FillError::EndGroupOverflow`] when the sum leaves the number space,
259 /// which Section 9.20.9 makes a PROTOCOL_VIOLATION at the receiver.
260 pub fn range(
261 start_group: u64,
262 start_object: u64,
263 end_group_delta: u64,
264 ) -> Result<Self, FillError> {
265 check_end_group(start_group, end_group_delta)?;
266 Ok(Self {
267 filter_type: location_filter_types::ABSOLUTE_START_GROUP_END,
268 fields: vec![start_group, start_object, end_group_delta],
269 })
270 }
271
272 /// Type 0x04: a fully specified **inclusive** range,
273 /// `{start_group, start_object}` through
274 /// `{start_group + end_group_delta, end_object}`.
275 ///
276 /// `end_object` is the Object ID of the last object the range covers. It is
277 /// not that Object ID plus one, and an `end_object` of 0 means object 0
278 /// rather than the whole group — both draft-19 conventions are absent from
279 /// this draft, and this is the constructor where a ported `+ 1` does its
280 /// damage.
281 ///
282 /// # Errors
283 ///
284 /// [`FillError::EndGroupOverflow`], as [`LocationFilter::range`].
285 pub fn range_to(
286 start_group: u64,
287 start_object: u64,
288 end_group_delta: u64,
289 end_object: u64,
290 ) -> Result<Self, FillError> {
291 check_end_group(start_group, end_group_delta)?;
292 Ok(Self {
293 filter_type: location_filter_types::ABSOLUTE_RANGE,
294 fields: vec![start_group, start_object, end_group_delta, end_object],
295 })
296 }
297
298 /// The `Location Filter Type`, one of
299 /// [`location_filter_types`].
300 pub fn filter_type(&self) -> u64 {
301 self.filter_type
302 }
303
304 /// The `vi64` fields that follow the type, in wire order. Empty for both
305 /// None and Next Object, which only [`LocationFilter::filter_type`] tells
306 /// apart.
307 pub fn fields(&self) -> &[u64] {
308 &self.fields
309 }
310
311 /// The encoded value, without the parameter type ahead of it: the
312 /// `Location Filter Type`, then the fields it names.
313 pub fn encode_value(&self) -> Vec<u8> {
314 let mut out = Vec::with_capacity(1 + self.fields.len() * 2);
315 VarInt::from_u64_moqt(self.filter_type).encode_moqt::<Wire>(&mut out);
316 for field in &self.fields {
317 VarInt::from_u64_moqt(*field).encode_moqt::<Wire>(&mut out);
318 }
319 out
320 }
321
322 /// This filter as the Message Parameter that carries it.
323 ///
324 /// # Errors
325 ///
326 /// [`FillError::NotRoundTrippable`] if the codec's own
327 /// `decode_location_filter` does not read back the type and fields that
328 /// went in.
329 pub fn parameter(&self) -> Result<KeyValuePair, FillError> {
330 let value = self.encode_value();
331 let (filter_type, fields) = decode_location_filter(&value)
332 .map_err(|e| FillError::NotRoundTrippable(e.to_string()))?;
333 if filter_type != self.filter_type || fields != self.fields {
334 return Err(FillError::NotRoundTrippable(
335 "the codec read back a different Location Filter Type or field list".to_string(),
336 ));
337 }
338 Ok(KeyValuePair {
339 key: VarInt::from_u64_moqt(LOCATION_FILTER),
340 value: KvpValue::Bytes(value),
341 })
342 }
343}
344
345fn check_end_group(start_group: u64, delta: u64) -> Result<(), FillError> {
346 start_group
347 .checked_add(delta)
348 .map(|_| ())
349 .ok_or(FillError::EndGroupOverflow { start_group, delta })
350}
351
352/// A draft-22 `FILL_PARAMETERS` (Parameter Type 0x23), Section 9.20.15.
353///
354/// Putting one of these on a SUBSCRIBE or on a REQUEST_UPDATE for a
355/// subscription is what asks the publisher to open a **fill fetch stream**: a
356/// unidirectional stream beginning with a FETCH_HEADER, delivered exactly as a
357/// FETCH response, carrying the Objects behind the live edge that the
358/// subscription itself will not deliver. Its mere presence is the request;
359/// [`FillParameters::inherited`] asks for a fill with every setting taken from
360/// the subscription.
361///
362/// The nested parameters override the subscription's for the fill alone. A
363/// `LOCATION_FILTER` nested here selects the **fill range** and is evaluated
364/// with Fetch rules, so it never reaches past Largest Object; it is independent
365/// of the subscription's own filter.
366///
367/// # This value begins with a `Number of Parameters` count
368///
369/// Section 9.20.15 says the value is "a sequence of Parameters that apply to
370/// the fill fetch stream" and is "encoded as if they were Parameters for a
371/// separate message", and stops there. **This crate reads that as the
372/// whole block, count included.** The draft does not state it either way. The
373/// grounds are Section 9.20's own definition of what a parameter block is —
374/// "Because unknown parameters cannot be skipped, the block is bounded by a
375/// parameter count rather than a length" — and the fact that every message
376/// figure in Section 9 pairs `Number of Parameters` with `Parameters`. The
377/// outer length prefix is the generic length-prefixed value encoding, and says
378/// nothing about the value's internal structure.
379///
380/// So an empty `FILL_PARAMETERS` is `Length = 1` carrying the single byte
381/// `0x00`, **not** `Length = 0`. Getting this wrong desynchronises the whole
382/// enclosing parameter list rather than producing a recognisable error, which
383/// is why it is named here as well as at the decoder.
384///
385/// # The `Type Delta` chain restarts here
386///
387/// Section 9.20.15: "The value of FILL_PARAMETERS is a separate parameter
388/// scope. Parameters inside it are not considered to appear in the enclosing
389/// message for the purposes of Section 9.20, so a Parameter Type MAY appear
390/// both in the message and inside FILL_PARAMETERS." Section 9.20 defines `Type
391/// Delta` against "the previous Parameter Type in the message", and a
392/// separate scope is not the message — so the inner chain starts from 0, and
393/// the outer parameter after `FILL_PARAMETERS` deltas from `0x23` rather than
394/// from the last inner type. **The draft states neither half**; both are
395/// decided here and in the codec's decoder, together.
396#[derive(Debug, Clone, Default, PartialEq, Eq)]
397pub struct FillParameters {
398 inner: Vec<KeyValuePair>,
399}
400
401impl FillParameters {
402 /// A fill with every setting inherited from the subscription.
403 ///
404 /// The common case, and the one whose encoding is worth knowing: one byte,
405 /// `0x00`, under a `Length` of 1.
406 pub fn inherited() -> Self {
407 Self::default()
408 }
409
410 /// Add a nested parameter. Types must be added in ascending order.
411 ///
412 /// # Errors
413 ///
414 /// [`FillError::NotAFillParameter`] for a type outside Table 7, and
415 /// [`FillError::RepeatedFillParameter`] for a second instance of a type
416 /// whose own definition does not allow repeats. Ordering is not checked
417 /// here — [`FillParameters::parameter`] sorts before encoding, because
418 /// Section 9.20 requires ascending order on the wire and a caller should
419 /// not have to know Table 7's numbering to satisfy it.
420 pub fn with(mut self, parameter: KeyValuePair) -> Result<Self, FillError> {
421 let key = parameter.key.into_inner();
422 if !FILL_PARAMETERS_ALLOWED.contains(&key) {
423 return Err(FillError::NotAFillParameter(key));
424 }
425 if !may_repeat(key) && self.inner.iter().any(|p| p.key.into_inner() == key) {
426 return Err(FillError::RepeatedFillParameter(key));
427 }
428 self.inner.push(parameter);
429 Ok(self)
430 }
431
432 /// Add the `LOCATION_FILTER` that selects the fill range.
433 ///
434 /// Shorthand for `with(filter.parameter()?)`, and the parameter most fills
435 /// carry: the fill range is what a fill is for.
436 pub fn with_range(self, filter: &LocationFilter) -> Result<Self, FillError> {
437 let parameter = filter.parameter()?;
438 self.with(parameter)
439 }
440
441 /// Add the `GROUP_ORDER` (0x22) that the fill fetch stream's Groups arrive
442 /// in.
443 ///
444 /// Section 9.20.8: "When it appears inside FILL_PARAMETERS, it governs the
445 /// fill fetch stream and its ordering relative to subscription-delivered
446 /// Objects". It is the one nested parameter a **reader** cannot ignore:
447 /// Section 11.4.1.1 makes a Group ID Delta count upward under Ascending and
448 /// downward under Descending, nothing on the data stream says which, and a
449 /// reader started in the wrong order decodes every Object after the first
450 /// into a Group that walks the wrong way — without failing to parse.
451 ///
452 /// A shorthand rather than a raw [`KeyValuePair`] because the value is a
453 /// uint8 on the wire while the crate's `KvpValue` has no uint8 arm: it
454 /// travels as a `Varint` whose value must fit one byte, and
455 /// `encode_nested_value` writes the byte. Getting that pair wrong is a
456 /// `MalformedValue` at best and a desynchronised block at worst.
457 ///
458 /// # Errors
459 ///
460 /// As [`FillParameters::with`]: [`FillError::RepeatedFillParameter`] for a
461 /// second `GROUP_ORDER`, which Section 9.20 does not allow.
462 pub fn with_group_order(self, order: GroupOrder) -> Result<Self, FillError> {
463 self.with(KeyValuePair {
464 key: VarInt::from_u64_moqt(GROUP_ORDER),
465 value: KvpValue::Varint(VarInt::from_u64_moqt(encode_group_order(order))),
466 })
467 }
468
469 /// The nested parameters, in the order they were added.
470 pub fn inner(&self) -> &[KeyValuePair] {
471 &self.inner
472 }
473
474 /// This block as the Message Parameter that carries it.
475 ///
476 /// The value is the count, then the parameters in ascending type order with
477 /// their types delta-encoded from 0, each value in the shape its own type
478 /// names. It is decoded back through the codec before it is returned.
479 ///
480 /// # Errors
481 ///
482 /// [`FillError::MalformedValue`] for a value whose shape does not match its
483 /// type, and [`FillError::NotRoundTrippable`] if the codec's
484 /// `decode_fill_parameters` refuses what this built or reads it back
485 /// differently.
486 pub fn parameter(&self) -> Result<KeyValuePair, FillError> {
487 let mut sorted = self.inner.clone();
488 // Section 9.20: "Parameters MUST be serialized in ascending order by
489 // Type." A stable sort keeps two instances of a repeatable filter in
490 // the order the caller added them, which is the order their SetIDs are
491 // meant to be read in.
492 sorted.sort_by_key(|p| p.key.into_inner());
493
494 let mut value = Vec::new();
495 VarInt::from_usize(sorted.len()).encode_moqt::<Wire>(&mut value);
496 let mut prev_key: u64 = 0;
497 for pair in &sorted {
498 let key = pair.key.into_inner();
499 // The chain starts at 0 because this is a separate scope, not
500 // because the block is at the start of a message.
501 let delta = key - prev_key;
502 prev_key = key;
503 VarInt::from_u64_moqt(delta).encode_moqt::<Wire>(&mut value);
504 encode_nested_value(key, &pair.value, &mut value)?;
505 }
506
507 let read = decode_fill_parameters(&value)
508 .map_err(|e| FillError::NotRoundTrippable(e.to_string()))?;
509 if read.len() != sorted.len() {
510 return Err(FillError::NotRoundTrippable(
511 "the codec read back a different number of parameters".to_string(),
512 ));
513 }
514 Ok(KeyValuePair {
515 key: VarInt::from_u64_moqt(FILL_PARAMETERS),
516 value: KvpValue::Bytes(value),
517 })
518 }
519}
520
521/// Write one nested parameter's value in the shape its type names.
522///
523/// The shapes are Table 7's eight types only, which is the whole of what may be
524/// nested; anything else was refused by [`FillParameters::with`] before it got
525/// here.
526fn encode_nested_value(key: u64, value: &KvpValue, out: &mut Vec<u8>) -> Result<(), FillError> {
527 match value {
528 KvpValue::Varint(v) if is_uint8(key) => {
529 let raw = v.into_inner();
530 let byte = u8::try_from(raw).map_err(|_| FillError::MalformedValue {
531 key,
532 detail: "a uint8 parameter's value does not fit one byte",
533 })?;
534 out.push(byte);
535 Ok(())
536 }
537 KvpValue::Varint(v) if is_bare_varint(key) => {
538 v.encode_moqt::<Wire>(out);
539 Ok(())
540 }
541 KvpValue::Bytes(bytes) if is_length_prefixed(key) => {
542 VarInt::from_usize(bytes.len()).encode_moqt::<Wire>(out);
543 out.extend_from_slice(bytes);
544 Ok(())
545 }
546 // Written as it stands: the reader finds the end of these bytes by
547 // reading the type, so a value whose fields disagree with its type is
548 // refused here rather than shifting every nested parameter after it.
549 KvpValue::Bytes(bytes) if key == LOCATION_FILTER => {
550 decode_location_filter(bytes).map_err(|_| FillError::MalformedValue {
551 key,
552 detail: "bytes that are not a Location Filter Type and the fields it names",
553 })?;
554 out.extend_from_slice(bytes);
555 Ok(())
556 }
557 KvpValue::Varint(_) => Err(FillError::MalformedValue {
558 key,
559 detail: "a bare varint where the type defines a structure",
560 }),
561 KvpValue::Bytes(_) => Err(FillError::MalformedValue {
562 key,
563 detail: "bytes where the type defines a bare varint or a uint8",
564 }),
565 }
566}
567
568/// The Section 9.20.8 value for a Group Order.
569fn encode_group_order(order: GroupOrder) -> u64 {
570 match order {
571 GroupOrder::Ascending => GROUP_ORDER_ASCENDING,
572 GroupOrder::Descending => GROUP_ORDER_DESCENDING,
573 }
574}
575
576/// The `GROUP_ORDER` in one parameter list, or `None` if it holds none.
577///
578/// One list, one level: the caller decides which scope this is being asked
579/// about, because the two scopes answer the question in a fixed order and only
580/// [`group_order`] knows that order.
581fn read_group_order(parameters: &[KeyValuePair]) -> Result<Option<GroupOrder>, FillError> {
582 let Some(parameter) = parameters.iter().find(|p| p.key.into_inner() == GROUP_ORDER) else {
583 return Ok(None);
584 };
585 let KvpValue::Varint(value) = ¶meter.value else {
586 return Err(FillError::MalformedValue {
587 key: GROUP_ORDER,
588 detail: "bytes where Section 9.20.8 defines a uint8",
589 });
590 };
591 match value.into_inner() {
592 GROUP_ORDER_ASCENDING => Ok(Some(GroupOrder::Ascending)),
593 GROUP_ORDER_DESCENDING => Ok(Some(GroupOrder::Descending)),
594 // The codec's decoder holds a received 0x22 to this same range before
595 // it ever reaches here (`uint8_value_in_range`), so this arm is for a
596 // pair a caller built in memory. Section 9.20.8: "If an endpoint
597 // receives a value outside this range, it MUST close the session with
598 // PROTOCOL_VIOLATION", so it is refused rather than rounded.
599 _ => Err(FillError::MalformedValue {
600 key: GROUP_ORDER,
601 detail: "Section 9.20.8 allows only Ascending (0x1) and Descending (0x2)",
602 }),
603 }
604}
605
606/// The Group Order a fill fetch stream's Objects arrive in, given the
607/// parameters of the SUBSCRIBE (or REQUEST_UPDATE) that asked for the fill.
608///
609/// A fill fetch stream is "delivered as a FETCH response" (Section 3.4), and
610/// a FETCH response's Group ID Deltas are read against a Group Order that is
611/// nowhere on the data stream — Section 11.4.1.1 makes a delta count upward
612/// under Ascending and downward under Descending. So a subscriber has to
613/// resolve the order from the control exchange before it reads the first
614/// Object, and this is that resolution. Hand the answer to
615/// [`begin_fetch_objects`](crate::draft22::connection::FramedRecvStream::begin_fetch_objects);
616/// [`accept_fill_stream`](crate::draft22::connection::Connection::accept_fill_stream)
617/// already does.
618///
619/// Three steps, in the order the two sections put them:
620///
621/// 1. A `GROUP_ORDER` **inside** `FILL_PARAMETERS`. Section 9.20.8: "When it
622/// appears inside FILL_PARAMETERS, it governs the fill fetch stream and its
623/// ordering relative to subscription-delivered Objects".
624/// 2. Otherwise the `GROUP_ORDER` on the request itself. Section 3.4: "The
625/// fill fetch stream inherits the subscription's parameters" and
626/// "parameters carried inside FILL_PARAMETERS override them for the fill
627/// fetch stream".
628/// 3. Otherwise **Ascending**.
629///
630/// # Step 3 is a choice the draft does not make
631///
632/// Section 9.20.8 gives two different defaults and a fill sits between them:
633/// "If omitted from SUBSCRIBE or SUBSCRIBE_TRACKS, the publisher's preference
634/// from the Track is used. If omitted from FETCH, the receiver uses Ascending
635/// (0x1)." A fill is asked for by a SUBSCRIBE and delivered as a FETCH
636/// response, so both sentences reach it. **This crate takes the FETCH
637/// default**, because the publisher's preference is not a thing a subscriber
638/// holds when the first Object arrives — it is a Track Property that arrives in
639/// SUBSCRIBE_OK at the earliest, and on this path may not arrive at all — while
640/// Ascending is a value the reader can be started with before the stream opens.
641/// A subscriber that does learn the publisher's preference and finds it
642/// Descending can restart the reader with `begin_fetch_objects` before reading
643/// the first Object.
644///
645/// # Errors
646///
647/// [`FillError::MalformedValue`] for a `GROUP_ORDER` whose value is not a uint8
648/// in `{1, 2}` or whose shape is not a bare number, and
649/// [`FillError::NotRoundTrippable`] for a `FILL_PARAMETERS` value the codec's
650/// own decoder refuses. Neither is reachable from a block this module built.
651pub fn group_order(parameters: &[KeyValuePair]) -> Result<GroupOrder, FillError> {
652 if let Some(fill) = parameters.iter().find(|p| p.key.into_inner() == FILL_PARAMETERS) {
653 let KvpValue::Bytes(value) = &fill.value else {
654 return Err(FillError::MalformedValue {
655 key: FILL_PARAMETERS,
656 detail: "a bare varint where Section 9.20.15 defines a parameter block",
657 });
658 };
659 let nested = decode_fill_parameters(value)
660 .map_err(|e| FillError::NotRoundTrippable(e.to_string()))?;
661 if let Some(order) = read_group_order(&nested)? {
662 return Ok(order);
663 }
664 }
665 if let Some(order) = read_group_order(parameters)? {
666 return Ok(order);
667 }
668 Ok(GroupOrder::Ascending)
669}
670
671/// Put `parameter` into `parameters` at the position ascending type order
672/// requires.
673///
674/// Section 9.20 makes the order a wire rule — "Parameters MUST be serialized in
675/// ascending order by Type" — and the codec's encoder refuses a descending
676/// pair rather than emitting one, so a caller appending a `LOCATION_FILTER` to
677/// a list that already holds a higher type would otherwise get an error from
678/// the encoder instead of a message.
679///
680/// A parameter of a type already present is inserted after the ones already
681/// there, which is the only placement that keeps a repeatable filter's
682/// instances in the order they were added.
683pub fn insert_parameter(parameters: &mut Vec<KeyValuePair>, parameter: KeyValuePair) {
684 let key = parameter.key.into_inner();
685 let at = parameters.iter().position(|p| p.key.into_inner() > key).unwrap_or(parameters.len());
686 parameters.insert(at, parameter);
687}
688
689#[cfg(test)]
690mod tests {
691 use super::*;
692
693 /// The empty fill is one byte, and that byte is the count: `Length = 1`
694 /// carrying `0x00`, not `Length = 0`.
695 ///
696 /// Encoding it as `Length = 0` instead fails with a decode error from the
697 /// codec, because a block with no count is a block whose first parameter
698 /// count is missing.
699 #[test]
700 fn an_empty_fill_is_a_single_zero_byte() {
701 let p = FillParameters::inherited().parameter().unwrap();
702 assert_eq!(p.key.into_inner(), FILL_PARAMETERS);
703 match p.value {
704 KvpValue::Bytes(bytes) => assert_eq!(bytes, vec![0x00]),
705 KvpValue::Varint(_) => panic!("FILL_PARAMETERS is length-prefixed"),
706 }
707 }
708
709 /// The inner chain starts at 0, so the first nested type is written as its
710 /// own number and not as a delta from the enclosing `0x23`.
711 #[test]
712 fn the_nested_type_delta_chain_restarts_at_zero() {
713 let fill = FillParameters::inherited()
714 .with_range(&LocationFilter::range_to(4, 0, 2, 7).unwrap())
715 .unwrap();
716 let p = fill.parameter().unwrap();
717 let KvpValue::Bytes(bytes) = p.value else { panic!("length-prefixed") };
718 // count = 1, then Type Delta = 0x21 (not 0x21 - 0x23, which would wrap),
719 // then Location Filter Type 0x04, then the four minimal one-byte
720 // fields. No length: the type says where the value ends.
721 assert_eq!(bytes, vec![0x01, 0x21, 0x04, 0x04, 0x00, 0x02, 0x07]);
722 }
723
724 /// A nested filter carries no length, which only a filter whose type and
725 /// field count differ can show: Next Object is the type alone, and a
726 /// parameter after it starts on the very next byte.
727 #[test]
728 fn a_nested_filter_is_written_without_a_length() {
729 let fill = FillParameters::inherited()
730 .with_range(&LocationFilter::next_object())
731 .unwrap()
732 .with_group_order(GroupOrder::Descending)
733 .unwrap()
734 .parameter()
735 .unwrap();
736 let KvpValue::Bytes(bytes) = fill.value else { panic!("length-prefixed") };
737 // count = 2; Type Delta 0x21, type 0x05; Type Delta 1 to 0x22, value 2.
738 assert_eq!(bytes, vec![0x02, 0x21, 0x05, 0x01, 0x02]);
739 let nested = decode_fill_parameters(&bytes).unwrap();
740 assert_eq!(nested.len(), 2);
741 }
742
743 /// Every type Section 9.20.9 defines, each with the fields its sentence
744 /// names. The type is what the decoder switches on, so the type is
745 /// asserted alongside the fields.
746 #[test]
747 fn each_constructor_is_one_location_filter_type() {
748 use location_filter_types as T;
749 let cases = [
750 (LocationFilter::none(), T::NONE, vec![]),
751 (LocationFilter::relative(3), T::RELATIVE_START, vec![3]),
752 (LocationFilter::absolute_start(1, 2), T::ABSOLUTE_START, vec![1, 2]),
753 (LocationFilter::range(1, 2, 3).unwrap(), T::ABSOLUTE_START_GROUP_END, vec![1, 2, 3]),
754 (LocationFilter::range_to(1, 2, 3, 4).unwrap(), T::ABSOLUTE_RANGE, vec![1, 2, 3, 4]),
755 (LocationFilter::next_object(), T::NEXT_OBJECT, vec![]),
756 ];
757 for (filter, filter_type, fields) in cases {
758 assert_eq!(filter.filter_type(), filter_type);
759 assert_eq!(filter.fields(), fields.as_slice());
760 let mut expected = vec![filter_type as u8];
761 expected.extend(fields.iter().map(|f| *f as u8));
762 assert_eq!(filter.encode_value(), expected);
763 assert!(filter.parameter().is_ok());
764 }
765 }
766
767 /// `{0, 0}` is the beginning of the track and Next Object is a type of its
768 /// own, so the two are different bytes. Section 9.20.9: "If Location
769 /// Filter Type is 0x05, no fields follow and it specifies the Next Object."
770 #[test]
771 fn an_absolute_start_at_zero_is_not_the_next_object() {
772 assert_eq!(LocationFilter::absolute_start(0, 0).encode_value(), vec![0x02, 0x00, 0x00]);
773 assert_eq!(LocationFilter::next_object().encode_value(), vec![0x05]);
774 assert_ne!(LocationFilter::absolute_start(0, 0), LocationFilter::next_object());
775 }
776
777 /// A field too large for one byte is where a type-led value and a
778 /// length-led one part company: the type stays 0x02 for two fields however
779 /// many bytes they take.
780 #[test]
781 fn the_type_counts_fields_not_bytes() {
782 let filter = LocationFilter::absolute_start(1000, 3);
783 let value = filter.encode_value();
784 assert_eq!(value[0], 0x02);
785 assert_eq!(value.len(), 4);
786 assert_eq!(decode_location_filter(&value).unwrap(), (0x02, vec![1000, 3]));
787 }
788
789 /// None and Next Object both carry no fields and mean opposite things, so
790 /// they are told apart by the type and never by the field list.
791 #[test]
792 fn none_and_next_object_differ_only_in_their_type() {
793 assert_eq!(LocationFilter::none().fields(), LocationFilter::next_object().fields());
794 assert_ne!(
795 LocationFilter::none().filter_type(),
796 LocationFilter::next_object().filter_type()
797 );
798 assert_eq!(LocationFilter::none().encode_value(), vec![0x00]);
799 }
800
801 /// The end is the last object, and nothing here adds one to it. Draft-19's
802 /// encoder wrote `last + 1`; a port that kept the arithmetic fetches one
803 /// object too many, and nothing on the wire distinguishes the two.
804 #[test]
805 fn an_end_object_is_the_last_object_and_not_one_past_it() {
806 let filter = LocationFilter::range_to(10, 0, 0, 5).unwrap();
807 assert_eq!(filter.fields(), &[10, 0, 0, 5]);
808 let KvpValue::Bytes(bytes) = filter.parameter().unwrap().value else { panic!() };
809 assert_eq!(bytes, vec![0x04, 10, 0, 0, 5]);
810 }
811
812 /// Section 9.20.9 answers the overflow with a session close, so it is
813 /// refused rather than emitted.
814 #[test]
815 fn an_end_group_past_the_number_space_is_refused() {
816 assert_eq!(
817 LocationFilter::range(u64::MAX, 0, 1),
818 Err(FillError::EndGroupOverflow { start_group: u64::MAX, delta: 1 })
819 );
820 assert!(LocationFilter::range_to(u64::MAX - 1, 0, 1, 0).is_ok());
821 }
822
823 /// Table 7 is a closed list, and the one Range Filter it leaves out is the
824 /// one that selects tracks rather than objects.
825 #[test]
826 fn a_type_outside_table_7_may_not_be_nested() {
827 let track_property_filter =
828 KeyValuePair { key: VarInt::from_u64_moqt(0x29), value: KvpValue::Bytes(vec![]) };
829 assert_eq!(
830 FillParameters::inherited().with(track_property_filter).unwrap_err(),
831 FillError::NotAFillParameter(0x29)
832 );
833 let expires = KeyValuePair {
834 key: VarInt::from_u64_moqt(0x08),
835 value: KvpValue::Varint(VarInt::from_u64_moqt(1)),
836 };
837 assert_eq!(
838 FillParameters::inherited().with(expires).unwrap_err(),
839 FillError::NotAFillParameter(0x08)
840 );
841 }
842
843 /// The order on the wire is ascending whatever order the caller used, so a
844 /// caller does not have to know Table 7's numbering.
845 #[test]
846 fn nested_parameters_go_out_in_ascending_type_order() {
847 let group_order = KeyValuePair {
848 key: VarInt::from_u64_moqt(0x22),
849 value: KvpValue::Varint(VarInt::from_u64_moqt(1)),
850 };
851 let priority = KeyValuePair {
852 key: VarInt::from_u64_moqt(0x20),
853 value: KvpValue::Varint(VarInt::from_u64_moqt(128)),
854 };
855 let fill = FillParameters::inherited()
856 .with(group_order)
857 .unwrap()
858 .with(priority)
859 .unwrap()
860 .parameter()
861 .unwrap();
862 let KvpValue::Bytes(bytes) = fill.value else { panic!() };
863 // count = 2, then 0x20 with value 128, then a delta of 2 to 0x22 with
864 // value 1 (Ascending).
865 assert_eq!(bytes, vec![0x02, 0x20, 128, 0x02, 0x01]);
866 }
867
868 /// A repeat of a type whose definition does not allow one is the frame the
869 /// codec refuses, so it is refused here first.
870 #[test]
871 fn a_non_repeatable_nested_type_may_not_appear_twice() {
872 let filter = LocationFilter::relative(0);
873 let err = FillParameters::inherited()
874 .with_range(&filter)
875 .unwrap()
876 .with_range(&filter)
877 .unwrap_err();
878 assert_eq!(err, FillError::RepeatedFillParameter(LOCATION_FILTER));
879 }
880
881 /// `INCLUDE_PROPERTIES` (0x35) is not a fill parameter, and the reason is
882 /// worth having on the record: Section 9.20.21 makes it govern the **Track
883 /// Properties** in an OK message, and a fill fetch stream has no OK message
884 /// at all (Section 3.4.1 — no FETCH_OK, no REQUEST_ERROR). It is absent
885 /// from Table 7, so nesting it is refused.
886 ///
887 /// The consequence for the data stream is the one to hold on to: the Object
888 /// Properties on a fill fetch object are governed by Serialization Flags bit
889 /// 0x20 (Section 11.4.1.1) and by nothing else. `INCLUDE_PROPERTIES` does
890 /// not gate them, on a fill or anywhere else.
891 #[test]
892 fn include_properties_is_not_a_fill_parameter() {
893 let include_properties = KeyValuePair {
894 key: VarInt::from_u64_moqt(0x35),
895 value: KvpValue::Varint(VarInt::from_u64_moqt(0)),
896 };
897 assert_eq!(
898 FillParameters::inherited().with(include_properties).unwrap_err(),
899 FillError::NotAFillParameter(0x35)
900 );
901 }
902
903 /// A `GROUP_ORDER` inside `FILL_PARAMETERS` is one byte under a `Type
904 /// Delta` counted from 0, and it is what the fill stream is read in.
905 #[test]
906 fn a_nested_group_order_is_the_order_the_fill_is_read_in() {
907 let fill = FillParameters::inherited()
908 .with_group_order(GroupOrder::Descending)
909 .unwrap()
910 .parameter()
911 .unwrap();
912 let KvpValue::Bytes(bytes) = &fill.value else { panic!("length-prefixed") };
913 // count = 1, Type Delta = 0x22 from the restarted chain, value = 0x02.
914 assert_eq!(bytes, &vec![0x01, 0x22, 0x02]);
915 assert_eq!(group_order(&[fill]).unwrap(), GroupOrder::Descending);
916 }
917
918 /// The nested order overrides the subscription's, which is what Section
919 /// 3.4 means by "parameters carried inside FILL_PARAMETERS override them
920 /// for the fill fetch stream".
921 #[test]
922 fn the_nested_group_order_beats_the_subscriptions_own() {
923 let subscription_order = KeyValuePair {
924 key: VarInt::from_u64_moqt(GROUP_ORDER),
925 value: KvpValue::Varint(VarInt::from_u64_moqt(1)),
926 };
927 let fill = FillParameters::inherited()
928 .with_group_order(GroupOrder::Descending)
929 .unwrap()
930 .parameter()
931 .unwrap();
932 let mut parameters = vec![fill];
933 insert_parameter(&mut parameters, subscription_order);
934 assert_eq!(group_order(¶meters).unwrap(), GroupOrder::Descending);
935 }
936
937 /// With nothing nested, the subscription's own `GROUP_ORDER` is inherited.
938 #[test]
939 fn a_fill_with_no_order_of_its_own_inherits_the_subscriptions() {
940 let mut parameters = vec![FillParameters::inherited().parameter().unwrap()];
941 insert_parameter(
942 &mut parameters,
943 KeyValuePair {
944 key: VarInt::from_u64_moqt(GROUP_ORDER),
945 value: KvpValue::Varint(VarInt::from_u64_moqt(2)),
946 },
947 );
948 assert_eq!(group_order(¶meters).unwrap(), GroupOrder::Descending);
949 }
950
951 /// With no `GROUP_ORDER` in either scope, Ascending. Section 9.20.8 gives a
952 /// fill two candidate defaults and this crate takes the FETCH one; see
953 /// [`group_order`].
954 #[test]
955 fn a_fill_that_names_no_order_anywhere_is_ascending() {
956 let parameters = vec![FillParameters::inherited().parameter().unwrap()];
957 assert_eq!(group_order(¶meters).unwrap(), GroupOrder::Ascending);
958 assert_eq!(group_order(&[]).unwrap(), GroupOrder::Ascending);
959 }
960
961 /// A `GROUP_ORDER` outside `{1, 2}` is refused rather than rounded to a
962 /// direction, because reading a stream in the wrong direction does not fail
963 /// to parse — it mis-locates every Object after the first.
964 #[test]
965 fn a_group_order_outside_the_two_values_is_refused() {
966 let parameters = vec![KeyValuePair {
967 key: VarInt::from_u64_moqt(GROUP_ORDER),
968 value: KvpValue::Varint(VarInt::from_u64_moqt(3)),
969 }];
970 assert!(matches!(
971 group_order(¶meters),
972 Err(FillError::MalformedValue { key: GROUP_ORDER, .. })
973 ));
974 }
975
976 /// A bare varint under `LOCATION_FILTER` is refused rather than written as
977 /// a Location Filter Type with no fields after it, which is what the codec's
978 /// encoder does with the same value outside a fill.
979 #[test]
980 fn a_bare_varint_location_filter_is_refused() {
981 for v in [0, 5] {
982 let result = FillParameters::inherited()
983 .with(KeyValuePair {
984 key: VarInt::from_u64_moqt(LOCATION_FILTER),
985 value: KvpValue::Varint(VarInt::from_u64_moqt(v)),
986 })
987 .and_then(|fill| fill.parameter());
988 assert!(
989 matches!(result, Err(FillError::MalformedValue { key: LOCATION_FILTER, .. })),
990 "a bare varint {v} under 0x21 gave {result:?}"
991 );
992 }
993 }
994
995 /// Ascending order is a wire rule the codec's encoder enforces, so the
996 /// helper that puts a parameter in a list has to respect it.
997 #[test]
998 fn insert_parameter_keeps_the_list_ascending() {
999 let mut params = vec![
1000 KeyValuePair { key: VarInt::from_u64_moqt(0x03), value: KvpValue::Bytes(vec![]) },
1001 KeyValuePair {
1002 key: VarInt::from_u64_moqt(0x35),
1003 value: KvpValue::Varint(VarInt::from_u64_moqt(1)),
1004 },
1005 ];
1006 insert_parameter(&mut params, LocationFilter::relative(0).parameter().unwrap());
1007 let keys: Vec<u64> = params.iter().map(|p| p.key.into_inner()).collect();
1008 assert_eq!(keys, vec![0x03, 0x21, 0x35]);
1009 }
1010}