moqtap_client/forwarding_preference.rs
1//! What a track's objects have been framed as, for the drafts that make that a
2//! property of the track.
3//!
4//! Nine drafts state one sentence about it, at draft-11 Section 9: "Every Track
5//! has a single 'Object Forwarding Preference' and the Original Publisher MUST
6//! NOT mix different forwarding preferences within a single track." Drafts 07
7//! through 10 and 12 through 15 say the same thing in the same words; what they
8//! disagree about is the answer, and that answer belongs to each draft's own
9//! close table rather than here. This module holds only the observation.
10//!
11//! # The framing is the preference
12//!
13//! No object header carries the value. An object on a subgroup stream has the
14//! Subgroup preference and an object in a datagram has the Datagram one, so a
15//! single object settles the property for its whole track and every object
16//! after it on that track has to agree. Draft-15 Section 10.2.1 says exactly
17//! that: "Note that the Original Publisher determines the Forwarding Preference
18//! for the entire Track, and is a Track property that is implicitly signaled by
19//! the delivery of any Object using either Subgroups or Datagrams. Once the
20//! property is established for one Object of a Track, the same value MUST be
21//! used for all Objects of the Track."
22//!
23//! Drafts 07 through 10 name a third preference in the enumeration and give it
24//! no framing to arrive in. Draft-08 Section 8.1.1 reads "The preferences are
25//! Track, Subgroup, and Datagram", while the stream type table three paragraphs
26//! above it lists SUBGROUP_HEADER and FETCH_HEADER and nothing else. It is a
27//! name an earlier draft left behind, and no peer can send one, so there are two
28//! values here and not three.
29//!
30//! # Why the key is the track and not the alias
31//!
32//! The wire names a track by its Track Alias, and the alias would be the cheaper
33//! key. It would also be wrong. An alias is free again the moment its
34//! subscription ends and may then name a different track, so a record kept
35//! against the alias would carry the first track's preference into the second
36//! and close a session over traffic the drafts permit. The alias is resolved to
37//! the track that holds it before anything is written down, and a track nothing
38//! holds an alias for is not recorded at all — objects for an alias no
39//! subscription named break a different rule, stated in a different sentence.
40//!
41//! # Why one record and not one per direction
42//!
43//! The property is the track's, not a subscription's and not a direction's, so
44//! an endpoint that publishes a track over subgroup streams and receives the
45//! same track's objects in datagrams has seen the sentence broken and this
46//! reports it. What each end does about it differs — a subscriber closes the
47//! session, a publisher declines to send — and that is the caller's to decide
48//! from the error, not this table's.
49
50use moqtap_codec::types::TrackNamespace;
51
52/// How a publisher sent an object, which the framing settles rather than any
53/// field on the wire.
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum ObjectForwardingPreference {
56 /// The object travelled on a subgroup stream.
57 Subgroup,
58 /// The object travelled in a datagram.
59 Datagram,
60}
61
62impl std::fmt::Display for ObjectForwardingPreference {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 match self {
65 ObjectForwardingPreference::Subgroup => f.write_str("Subgroup"),
66 ObjectForwardingPreference::Datagram => f.write_str("Datagram"),
67 }
68 }
69}
70
71/// One track and the preference its first object settled.
72struct TrackPreference {
73 namespace: TrackNamespace,
74 name: Vec<u8>,
75 settled: ObjectForwardingPreference,
76}
77
78/// The preference each track's objects have been framed as, so far.
79///
80/// A session holds a handful of tracks and this is scanned once per data stream
81/// and once per datagram, which is why it is a list rather than a map — the same
82/// shape, and for the same reason, as the endpoint's own table of track aliases.
83#[derive(Default)]
84pub struct TrackForwardingPreferences {
85 tracks: Vec<TrackPreference>,
86}
87
88impl TrackForwardingPreferences {
89 /// An empty record, for a session that has carried no objects yet.
90 pub fn new() -> Self {
91 Self { tracks: Vec::new() }
92 }
93
94 /// Record that a track's object was framed as `seen`.
95 ///
96 /// `Ok` when the track had settled on `seen` already or had settled on
97 /// nothing. `Err` carries the preference the track did settle on, which is
98 /// the half of the report that says what the rule was broken against.
99 pub fn observe(
100 &mut self,
101 namespace: &TrackNamespace,
102 name: &[u8],
103 seen: ObjectForwardingPreference,
104 ) -> Result<(), ObjectForwardingPreference> {
105 for track in &self.tracks {
106 if track.namespace == *namespace && track.name == name {
107 return if track.settled == seen { Ok(()) } else { Err(track.settled) };
108 }
109 }
110 self.tracks.push(TrackPreference {
111 namespace: namespace.clone(),
112 name: name.to_vec(),
113 settled: seen,
114 });
115 Ok(())
116 }
117}