Skip to main content

moqtap_codec/fields/
mod.rs

1//! A decoded control message as a tree of named fields.
2//!
3//! [`AnyControlMessage::fields`](crate::dispatch::AnyControlMessage::fields)
4//! turns any draft's `ControlMessage` into a [`FieldMap`] whose keys are the
5//! field names that draft gives them. The names are the drafts' own, in
6//! snake_case, and the field order is the order the draft defines — so a
7//! reader that has never heard of a message can still show it, and two drafts
8//! that spell the same concept differently keep their own spelling. The
9//! per-draft `fields` modules are what it dispatches to.
10//!
11//! # Why a tree of the crate's own making
12//!
13//! The obvious return types are `serde_json::Value` and `ciborium::Value`, and
14//! this crate depends on neither. A codec that gained a serialization format's
15//! value type would make everything downstream carry it, to describe messages
16//! that have nothing to do with that format. [`FieldValue`] is `std` and a
17//! `Vec`, and each caller renders it into whatever it already writes: the
18//! vector tests into JSON, where a varint becomes a decimal string and a byte
19//! string becomes hex, and a trace writer into CBOR, where both have a type of
20//! their own.
21//!
22//! That split is also why [`FieldValue::Uint`] and [`FieldValue::Bytes`] are
23//! distinct from [`FieldValue::Text`] rather than pre-rendered into it. A
24//! converter that flattened them would force every consumer to guess which
25//! strings were numbers.
26
27/// Parameter tables more than one draft shares.
28///
29/// A draft whose parameter handling is its own keeps it in its own `fields.rs`,
30/// which is where all but four of them are. Only drafts 07 through 10 share:
31/// one message table across all four, and one setup table across the three that
32/// dropped ROLE. Anything reachable from a single draft belongs to that draft,
33/// or a build of that draft alone compiles code nothing can call.
34#[cfg(any(feature = "draft07", feature = "draft08", feature = "draft09", feature = "draft10"))]
35pub(crate) mod params;
36
37/// One field's value inside a decoded control message.
38///
39/// Absent optional fields are omitted from their [`FieldMap`] rather than
40/// given a zero: a field the wire never carried and a field carrying zero are
41/// different, and only omission can say so.
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub enum FieldValue {
44    /// A varint or fixed-width integer, widened to `u64`.
45    Uint(u64),
46    /// A single-bit field.
47    Bool(bool),
48    /// A field the draft defines as text, or a name for something the draft
49    /// leaves opaque — an unknown parameter's key, rendered `0x21`.
50    Text(String),
51    /// A field the draft leaves as opaque bytes.
52    Bytes(Vec<u8>),
53    /// A repeated field, in wire order.
54    Array(Vec<FieldValue>),
55    /// A nested structure — a location, a parameter set, a fetch's payload.
56    Map(FieldMap),
57}
58
59/// A decoded message's fields, in the order the draft defines them.
60///
61/// Ordered rather than sorted because the order is information: it is the
62/// order the fields appear on the wire, which is what makes a rendering of
63/// one message comparable to a rendering of the same message from another
64/// implementation.
65#[derive(Debug, Clone, Default, PartialEq, Eq)]
66pub struct FieldMap {
67    entries: Vec<(String, FieldValue)>,
68}
69
70impl FieldMap {
71    /// An empty map.
72    pub fn new() -> Self {
73        Self { entries: Vec::new() }
74    }
75
76    /// Set `key` to `value`, replacing any value already under that key.
77    ///
78    /// Replacing rather than appending keeps a duplicate key impossible, which
79    /// is what lets a reader index the map. A replaced key keeps its original
80    /// position, so a later correction does not reorder the message.
81    ///
82    /// The key is a `String` rather than an `impl Into<String>` because the
83    /// callers write `"request_id".into()`, and a generic bound leaves that
84    /// `into` with nothing to infer from.
85    pub fn insert(&mut self, key: String, value: FieldValue) {
86        match self.entries.iter_mut().find(|(k, _)| *k == key) {
87            Some(entry) => entry.1 = value,
88            None => self.entries.push((key, value)),
89        }
90    }
91
92    /// The value under `key`, if the message carried that field.
93    pub fn get(&self, key: &str) -> Option<&FieldValue> {
94        self.entries.iter().find(|(k, _)| k == key).map(|(_, v)| v)
95    }
96
97    /// The fields, in the order the draft defines them.
98    pub fn iter(&self) -> impl Iterator<Item = (&str, &FieldValue)> {
99        self.entries.iter().map(|(k, v)| (k.as_str(), v))
100    }
101
102    /// Whether the message had no fields at all. True for the handful of
103    /// messages that are nothing but their type.
104    pub fn is_empty(&self) -> bool {
105        self.entries.is_empty()
106    }
107
108    /// How many fields the message carried.
109    pub fn len(&self) -> usize {
110        self.entries.len()
111    }
112}
113
114impl IntoIterator for FieldMap {
115    type Item = (String, FieldValue);
116    type IntoIter = std::vec::IntoIter<(String, FieldValue)>;
117
118    fn into_iter(self) -> Self::IntoIter {
119        self.entries.into_iter()
120    }
121}
122
123/// Render a Key-Value-Pair list as entries, in the order the wire carried them.
124///
125/// # Why a list and not a map keyed by name
126///
127/// Every draft models a parameter block as an ordered list of (Type, Value),
128/// with types ascending. Rendering it as a map keyed by the draft's name for
129/// each type reads better and loses three things:
130///
131/// * **Repeats.** Two parameter definitions permit a message to carry their
132///   type more than once — AUTHORIZATION_TOKEN, and on drafts 19 and 20 the
133///   five Range Filters. A map has one slot per name, so two SUBGROUP_FILTERs
134///   under different SetIDs became the second one alone: the frame said
135///   "Subgroup 1-3 in set 0, or 10-12 in set 1" and the record said "10-12",
136///   which is not a narrower reading of the request but a different one.
137/// * **Order.** Drafts 16 and later require that "Parameters MUST be serialized
138///   in ascending order by Type" and answer a descending pair with a session
139///   close. A map has no order, so no vector could state that rule at all.
140/// * **Unknown types.** A map has no name to key them under, so each draft
141///   invented something: 11 through 14 dropped them, and the later ones parked
142///   them in a second, differently-shaped `unknown` array beside the named
143///   ones. Two containers for one wire field.
144///
145/// An entry list has none of those problems and needs no special case for any
146/// of them: a repeat is two entries, order is the list's, and an unknown type
147/// is an entry without a `name`.
148///
149/// # The entry
150///
151/// `type` is always present, as the lowercase hex of the Parameter Type. `name`
152/// is present when the draft names that type. Then exactly one of:
153///
154/// * `value` — the decoded value, when this codec models it. A varint renders
155///   as its number, a structure as a nested map.
156/// * `raw_hex` — the value's bytes, when it does not.
157///
158/// An unnamed varint parameter gets `value` rather than `raw_hex`, because a
159/// varint's value *is* its content and there are no bytes to show. `length` is
160/// the key that does not belong here: it would hold the varint's value under a
161/// name that promises the byte count of something else.
162///
163/// # Why the `allow`, and what it cannot hide
164///
165/// Every caller is a parameter renderer, and every parameter renderer belongs
166/// to a draft: `fields::params` for drafts 07 through 10, `draftNN::fields` for
167/// 11 through 20. So the build that compiled **no** draft has this function and
168/// nothing that calls it, and that build is a standing CI row twice over —
169/// `just test-features`'s `no drafts` clippy, and the two zero-draft rows of
170/// `just draft-matrix`, all three under `-D warnings`.
171///
172/// A `cfg` here would have to name all fourteen drafts, and it would then have
173/// to be repeated on this module's own unit tests, which call this function and
174/// name no draft at all: `--all-targets` compiles them, so gating the function
175/// without gating them turns a dead-code warning into a build failure in the
176/// very row it was meant to fix. The `allow` is one line and has no second copy
177/// to fall out of step with.
178///
179/// It conceals nothing in a build that has a draft. One draft is enough to give
180/// this a caller, so the lint is live on all fourteen single-draft rows and on
181/// every build a user will ever make; the allow is inert everywhere except the
182/// build where the function is *correctly* unused.
183#[allow(dead_code)]
184pub(crate) fn kvp_entries<F>(params: &[crate::kvp::KeyValuePair], mut render: F) -> FieldValue
185where
186    F: FnMut(u64, &crate::kvp::KvpValue) -> (Option<&'static str>, Option<FieldValue>),
187{
188    use crate::kvp::KvpValue;
189
190    let mut out = Vec::with_capacity(params.len());
191    for p in params {
192        let key = p.key.into_inner();
193        let (name, value) = render(key, &p.value);
194
195        let mut entry = FieldMap::new();
196        entry.insert("type".into(), FieldValue::Text(format!("0x{key:x}")));
197        if let Some(name) = name {
198            entry.insert("name".into(), FieldValue::Text(name.to_string()));
199        }
200        match (value, &p.value) {
201            (Some(value), _) => entry.insert("value".into(), value),
202            (None, KvpValue::Varint(v)) => {
203                entry.insert("value".into(), FieldValue::Uint(v.into_inner()))
204            }
205            (None, KvpValue::Bytes(b)) => {
206                entry.insert("raw_hex".into(), FieldValue::Bytes(b.clone()))
207            }
208        }
209        out.push(FieldValue::Map(entry));
210    }
211    FieldValue::Array(out)
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217    use crate::kvp::{KeyValuePair, KvpValue};
218    use crate::varint::VarInt;
219
220    fn pair(key: u64, value: KvpValue) -> KeyValuePair {
221        KeyValuePair { key: VarInt::from_u64(key).expect("a fixture key"), value }
222    }
223
224    fn field(entry: &FieldValue, name: &str) -> Option<FieldValue> {
225        match entry {
226            FieldValue::Map(m) => m.get(name).cloned(),
227            _ => panic!("an entry is a map"),
228        }
229    }
230
231    fn entries(value: &FieldValue) -> &[FieldValue] {
232        match value {
233            FieldValue::Array(items) => items,
234            _ => panic!("a KVP list renders as an array"),
235        }
236    }
237
238    /// A repeat is two entries, which is the whole of the special case.
239    ///
240    /// The map this replaced had one slot per name, so the first of these
241    /// vanished and the second was reported as if it were all the frame
242    /// carried.
243    #[test]
244    fn a_repeated_type_is_two_entries_in_wire_order() {
245        let params =
246            vec![pair(0x25, KvpValue::Bytes(vec![0x00])), pair(0x25, KvpValue::Bytes(vec![0x01]))];
247        let rendered =
248            kvp_entries(&params, |key, _| (Some("subgroup_filter"), Some(FieldValue::Uint(key))));
249        let items = entries(&rendered);
250        assert_eq!(items.len(), 2);
251        for entry in items {
252            assert_eq!(field(entry, "type"), Some(FieldValue::Text("0x25".into())));
253            assert_eq!(field(entry, "name"), Some(FieldValue::Text("subgroup_filter".into())));
254        }
255    }
256
257    /// Order is the list's, so a descending pair renders as one.
258    ///
259    /// Drafts 16 and later close the session over a descending pair, and a map
260    /// keyed by name could not state the rule because it had no order to be
261    /// wrong about.
262    #[test]
263    fn order_survives_and_is_the_wire_order() {
264        let params = vec![
265            pair(0x20, KvpValue::Varint(VarInt::from_u64(1).unwrap())),
266            pair(0x10, KvpValue::Varint(VarInt::from_u64(2).unwrap())),
267        ];
268        let rendered = kvp_entries(&params, |_, _| (None, None));
269        let items = entries(&rendered);
270        assert_eq!(field(&items[0], "type"), Some(FieldValue::Text("0x20".into())));
271        assert_eq!(field(&items[1], "type"), Some(FieldValue::Text("0x10".into())));
272    }
273
274    /// An unnamed type is an ordinary entry without a `name`, not a second
275    /// container beside the named ones.
276    #[test]
277    fn an_unknown_type_keeps_its_bytes_and_loses_only_its_name() {
278        let params = vec![pair(0xf1, KvpValue::Bytes(vec![0xaa, 0xbb]))];
279        let rendered = kvp_entries(&params, |_, _| (None, None));
280        let entry = &entries(&rendered)[0];
281        assert_eq!(field(entry, "type"), Some(FieldValue::Text("0xf1".into())));
282        assert_eq!(field(entry, "name"), None);
283        assert_eq!(field(entry, "raw_hex"), Some(FieldValue::Bytes(vec![0xaa, 0xbb])));
284        assert_eq!(field(entry, "value"), None);
285    }
286
287    /// An unnamed *varint* gets `value`, because there are no bytes to show.
288    ///
289    /// `length` is the key that must not appear: it would hold the varint's
290    /// value under a name that promises the byte count of something else, which
291    /// is why the absence assertion below names that key in particular.
292    #[test]
293    fn an_unknown_varint_reports_its_value_rather_than_a_length() {
294        let params = vec![pair(0xf0, KvpValue::Varint(VarInt::from_u64(4).unwrap()))];
295        let rendered = kvp_entries(&params, |_, _| (None, None));
296        let entry = &entries(&rendered)[0];
297        assert_eq!(field(entry, "value"), Some(FieldValue::Uint(4)));
298        assert_eq!(field(entry, "raw_hex"), None);
299        assert_eq!(field(entry, "length"), None);
300    }
301}