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