Skip to main content

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}