Skip to main content

moqtap_codec/draft20/
fields.rs

1use crate::draft20::message::ControlMessage;
2use crate::fields::{FieldMap as Map, FieldValue as Value};
3use crate::kvp::{KeyValuePair, KvpValue};
4use crate::range_filter::RangeFilter;
5use crate::types::*;
6use crate::varint::{Moqt18 as Wire, VarInt};
7
8fn vi(v: u64) -> Value {
9    Value::Uint(v)
10}
11
12fn ns_to_json(ns: &TrackNamespace) -> Value {
13    Value::Array(
14        ns.0.iter().map(|e| Value::Text(String::from_utf8_lossy(e).into_owned())).collect(),
15    )
16}
17
18// Draft-20 known parameter types and their encodings. Two rows are new:
19// 0x23 FILL_PARAMETERS and 0x35 INCLUDE_PROPERTIES. Nothing was removed or
20// renumbered.
21fn d20_param_name(key: u64) -> Option<&'static str> {
22    match key {
23        0x02 => Some("object_delivery_timeout"),
24        0x03 => Some("authorization_token"),
25        0x04 => Some("rendezvous_timeout"),
26        0x06 => Some("subgroup_delivery_timeout"),
27        0x08 => Some("expires"),
28        0x09 => Some("largest_object"),
29        0x0A => Some("fill_timeout"),
30        0x10 => Some("forward"),
31        0x20 => Some("subscriber_priority"),
32        0x21 => Some("location_filter"),
33        0x22 => Some("group_order"),
34        0x23 => Some("fill_parameters"),
35        0x25 => Some("subgroup_filter"),
36        0x26 => Some("objectid_filter"),
37        0x27 => Some("priority_filter"),
38        0x28 => Some("object_property_filter"),
39        0x29 => Some("track_property_filter"),
40        0x32 => Some("new_group_request"),
41        0x34 => Some("track_namespace_prefix"),
42        0x35 => Some("include_properties"),
43        _ => None,
44    }
45}
46
47// Draft-20 setup option names — unchanged from draft-19.
48fn d20_option_name(key: u64) -> Option<&'static str> {
49    match key {
50        0x01 => Some("path"),
51        0x03 => Some("authorization_token"),
52        0x04 => Some("max_auth_token_cache_size"),
53        0x05 => Some("authority"),
54        0x06 => Some("max_filter_ranges"),
55        0x07 => Some("moqt_implementation"),
56        0x08 => Some("max_request_updates"),
57        _ => None,
58    }
59}
60
61/// Render a draft-20 Range Filter parameter value: SetID (u8), an optional
62/// Property Type (varint, for the Object/Track Property filters), then a
63/// sequence of delta-encoded inclusive Start/End range pairs. A zero-length
64/// value denotes filter removal (only meaningful in REQUEST_UPDATE).
65///
66/// Unchanged from draft-19: draft-20 replaced the one-line shorthand of its
67/// Section 5.1.3 with proper figures in Section 5.1.4 and changed no codepoint,
68/// field order or delta arithmetic.
69///
70/// # The parse belongs to [`crate::range_filter`], not here
71///
72/// Draft-20's parameter table gives 0x25-0x29 `LengthPrefixed`, which stores
73/// the value verbatim, and `check_location_filters` covers 0x21 and 0x23 and
74/// nothing else — so the bytes arriving here are whatever a peer sent, one
75/// level down inside `FILL_PARAMETERS` as well as at the top. A parse written
76/// out again here read its varints with `unwrap` and resolved its two delta
77/// baselines with `+`, on a value where `Buf::has_remaining` promises one more
78/// byte and a MoQT varint may need nine. The bare `+` was the worse half: in
79/// release it wrapped rather than panicking, and a filter recorded as
80/// `start = u64::MAX, end = 0` is a wrong answer that looks like data.
81///
82/// [`RangeFilter::decode_moqt`] is the same read done once, with `checked_add`
83/// on both baselines and a `Malformed` for every field the value ends before.
84/// Calling it makes the checked parse the only parse.
85///
86/// # What a value it cannot read renders as
87///
88/// The raw bytes. [`message_fields`] answers for a message that has *already*
89/// decoded, so refusing is not available to it: the frame is valid and one
90/// parameter's value is not. Rendering what arrived is the answer
91/// `fields::params` gives in the same situation, and the one this file's own
92/// `auth_token_to_json_d20` and `decode_track_namespace_prefix` already give.
93///
94/// # A filter that parses but breaks a content rule still renders
95///
96/// [`RangeFilter`] enforces two rules beyond the value's shape — a Publisher
97/// Priority range above 255, and a property filter over an odd Property Type.
98/// Draft-20 answers both with REQUEST_ERROR rather than a session close, so
99/// both arrive here as bytes a peer really sent.
100///
101/// Those two are exactly the filters whose fields a reader most needs to see,
102/// so this renders them and names the rule broken under `violates`, rather
103/// than refusing and hiding the offending value inside a hex dump. That is why
104/// it decodes with [`RangeFilter::decode_moqt_structure`] and asks
105/// [`RangeFilter::check_its_own_types`] separately: a decoder must refuse these
106/// values, a renderer must describe them, and the split is by what the caller
107/// does with the answer rather than by how much checking it wants.
108fn decode_range_filter(bytes: &[u8], parameter_type: u64) -> Value {
109    let mut o = Map::new();
110    if bytes.is_empty() {
111        o.insert("removed".into(), Value::Bool(true));
112        return Value::Map(o);
113    }
114    let Ok(filter) = RangeFilter::decode_moqt_structure::<Wire>(parameter_type, bytes) else {
115        return Value::Bytes(bytes.to_vec());
116    };
117    if let Err(broken) = filter.check_its_own_types() {
118        o.insert("violates".into(), Value::Text(broken.to_string()));
119    }
120    o.insert("set_id".into(), vi(filter.set_id as u64));
121    if let Some(property_type) = filter.property_type {
122        o.insert("property_type".into(), vi(property_type));
123    }
124    let ranges = filter
125        .ranges
126        .iter()
127        .map(|range| {
128            let mut r = Map::new();
129            r.insert("start".into(), vi(range.start));
130            if let Some(end) = range.end {
131                r.insert("end".into(), vi(end));
132            }
133            Value::Map(r)
134        })
135        .collect();
136    o.insert("ranges".into(), Value::Array(ranges));
137    Value::Map(o)
138}
139
140/// Decode a draft-20 LOCATION_FILTER value.
141///
142/// The Filter Type enum is gone — draft-19's `0x1` Next Group Start, `0x2`
143/// Largest Object, `0x3` AbsoluteStart and `0x4` AbsoluteRange — and the shape
144/// comes from how many `vi64` fields the value holds instead (Section 5.1.2).
145/// The names are positional: one field is `StartGroup`, two adds
146/// `StartObject`, three adds `EndGroupDelta`, four adds `EndObject`, and a
147/// zero-length value is the filter's removal.
148///
149/// The fields are decoded through the codec rather than restated here, so this
150/// reports what the codec read.
151fn decode_location_filter(bytes: &[u8]) -> Value {
152    const NAMES: [&str; 4] = ["start_group", "start_object", "end_group_delta", "end_object"];
153    let fields = crate::draft20::message::decode_location_filter(bytes)
154        .expect("the decoder accepted this parameter, so its value parses");
155    let mut obj = Map::new();
156    if fields.is_empty() {
157        obj.insert("removed".into(), Value::Bool(true));
158        return Value::Map(obj);
159    }
160    for (name, value) in NAMES.iter().zip(fields) {
161        obj.insert((*name).to_string(), vi(value));
162    }
163    Value::Map(obj)
164}
165
166/// Decode a draft-20 FILL_PARAMETERS value: a nested parameter block in its own
167/// scope, reported the same way an outer block is.
168///
169/// The nesting is what makes this recursive with [`params_to_json`] — a fill's
170/// LOCATION_FILTER is rendered exactly as the subscription's is — and it
171/// bottoms out because Table 6 does not list FILL_PARAMETERS inside itself.
172fn decode_fill_parameters(bytes: &[u8]) -> Value {
173    let nested = crate::draft20::message::decode_fill_parameters(bytes)
174        .expect("the decoder accepted this parameter, so its value parses");
175    params_to_json(&nested)
176}
177
178fn auth_token_to_json_d20(bytes: &[u8]) -> Value {
179    let mut buf = bytes;
180    let alias_type = match VarInt::decode_moqt::<Wire>(&mut buf) {
181        Ok(v) => v,
182        Err(_) => return Value::Bytes(bytes.to_vec()),
183    };
184    let at = alias_type.into_inner();
185    let mut o = Map::new();
186    o.insert("alias_type".into(), vi(at));
187    match at {
188        0 | 2 => {
189            if let Ok(ta) = VarInt::decode_moqt::<Wire>(&mut buf) {
190                o.insert("token_alias".into(), vi(ta.into_inner()));
191            }
192        }
193        1 => {
194            if let Ok(ta) = VarInt::decode_moqt::<Wire>(&mut buf) {
195                o.insert("token_alias".into(), vi(ta.into_inner()));
196            }
197            if let Ok(tt) = VarInt::decode_moqt::<Wire>(&mut buf) {
198                o.insert("token_type".into(), vi(tt.into_inner()));
199            }
200            // Draft-18 onwards: token_value runs to end of bytes (no inner
201            // length).
202            o.insert("token_value".into(), Value::Bytes(buf.to_vec()));
203        }
204        _ => {
205            if let Ok(tt) = VarInt::decode_moqt::<Wire>(&mut buf) {
206                o.insert("token_type".into(), vi(tt.into_inner()));
207            }
208            o.insert("token_value".into(), Value::Bytes(buf.to_vec()));
209        }
210    }
211    Value::Map(o)
212}
213
214/// Render a LARGEST OBJECT (0x09) parameter value: a Group and an Object, as
215/// two varints.
216///
217/// # What a value it cannot read renders as
218///
219/// The raw bytes, as `fields::params` does. Field extraction runs on a message
220/// that has already decoded, so it has no refusal to give, and it has no
221/// guarantee the two varints are there: nothing between `KeyValuePair::decode`
222/// and here looks at a 0x09 value's contents. An empty value fails the first
223/// read and a single `0x00` fails the second, which is the nastier of the two —
224/// the value looks well formed right up to the point where it is not. Neither
225/// read may panic on it.
226fn decode_largest_object(bytes: &[u8]) -> Value {
227    let mut buf = bytes;
228    let Ok(group) = VarInt::decode_moqt::<Wire>(&mut buf) else {
229        return Value::Bytes(bytes.to_vec());
230    };
231    let Ok(object) = VarInt::decode_moqt::<Wire>(&mut buf) else {
232        return Value::Bytes(bytes.to_vec());
233    };
234    let (group, object) = (group.into_inner(), object.into_inner());
235    let mut obj = Map::new();
236    obj.insert("group".into(), vi(group));
237    obj.insert("object".into(), vi(object));
238    Value::Map(obj)
239}
240
241fn decode_track_namespace_prefix(bytes: &[u8]) -> Value {
242    let mut buf = bytes;
243    match TrackNamespace::decode_allow_empty_moqt::<Wire>(&mut buf) {
244        Ok(ns) => ns_to_json(&ns),
245        Err(_) => Value::Bytes(bytes.to_vec()),
246    }
247}
248
249fn params_to_json(params: &[KeyValuePair]) -> Value {
250    crate::fields::kvp_entries(params, |key, value| {
251        let Some(name) = d20_param_name(key) else {
252            return (None, None);
253        };
254        let rendered = match (value, key) {
255            (KvpValue::Bytes(b), 0x21) => decode_location_filter(b),
256            (KvpValue::Bytes(b), 0x23) => decode_fill_parameters(b),
257            // One arm for all five: which of them carries a Property Type
258            // is a property of the type, and `range_filter` is the one
259            // place that decides it. Two arms passing a bool were two
260            // chances to answer it differently.
261            (KvpValue::Bytes(b), 0x25..=0x29) => decode_range_filter(b, key),
262            (KvpValue::Bytes(b), 0x09) => decode_largest_object(b),
263            (KvpValue::Bytes(b), 0x34) => decode_track_namespace_prefix(b),
264            (KvpValue::Bytes(b), _) if name == "authorization_token" => auth_token_to_json_d20(b),
265            (KvpValue::Varint(v), _) => vi(v.into_inner()),
266            (KvpValue::Bytes(b), _) => Value::Text(String::from_utf8_lossy(b).into_owned()),
267        };
268        (Some(name), Some(rendered))
269    })
270}
271
272pub(crate) fn options_to_json(options: &[KeyValuePair]) -> Value {
273    crate::fields::kvp_entries(options, |key, value| {
274        let Some(name) = d20_option_name(key) else {
275            return (None, None);
276        };
277        let rendered = match value {
278            KvpValue::Varint(v) => vi(v.into_inner()),
279            KvpValue::Bytes(b) if name == "authorization_token" => auth_token_to_json_d20(b),
280            KvpValue::Bytes(b) => Value::Text(String::from_utf8_lossy(b).into_owned()),
281        };
282        (Some(name), Some(rendered))
283    })
284}
285
286fn d20_track_prop_name(key: u64) -> Option<&'static str> {
287    match key {
288        0x02 => Some("object_delivery_timeout"),
289        0x04 => Some("max_cache_duration"),
290        0x06 => Some("subgroup_delivery_timeout"),
291        0x0b => Some("immutable_properties"),
292        0x0e => Some("default_publisher_priority"),
293        0x22 => Some("default_publisher_group_order"),
294        0x30 => Some("dynamic_groups"),
295        _ => None,
296    }
297}
298
299fn track_props_to_json(props: &[KeyValuePair]) -> Value {
300    crate::fields::kvp_entries(props, |key, value| {
301        let name = d20_track_prop_name(key);
302        let rendered = match value {
303            KvpValue::Varint(v) => Some(vi(v.into_inner())),
304            // A property this draft does not name keeps its bytes rather than
305            // a name invented from its type, which is what an entry's absent
306            // `name` already says.
307            KvpValue::Bytes(_) => None,
308        };
309        (name, rendered)
310    })
311}
312
313/// This draft's field names for a decoded control message.
314///
315/// Keys are the names this draft gives its fields, in the order it defines
316/// them. An optional field the message did not carry is absent rather than
317/// zero.
318pub fn message_fields(msg: &ControlMessage) -> Map {
319    let obj = match msg {
320        ControlMessage::Setup(m) => {
321            let mut o = Map::new();
322            o.insert("options".into(), options_to_json(&m.options));
323            o
324        }
325        ControlMessage::GoAway(m) => {
326            let mut o = Map::new();
327            o.insert(
328                "new_session_uri".into(),
329                Value::Text(String::from_utf8_lossy(&m.new_session_uri).into_owned()),
330            );
331            o.insert("timeout".into(), vi(m.timeout.into_inner()));
332            o
333        }
334        ControlMessage::RequestOk(m) => {
335            let mut o = Map::new();
336            o.insert("parameters".into(), params_to_json(&m.parameters));
337            o.insert("track_properties".into(), track_props_to_json(&m.track_properties));
338            o
339        }
340        ControlMessage::RequestError(m) => {
341            let mut o = Map::new();
342            o.insert("error_code".into(), vi(m.error_code.into_inner()));
343            o.insert("retry_interval".into(), vi(m.retry_interval.into_inner()));
344            o.insert(
345                "reason_phrase".into(),
346                Value::Text(String::from_utf8_lossy(&m.reason_phrase).into_owned()),
347            );
348            if let Some(r) = &m.redirect {
349                let mut r_obj = Map::new();
350                r_obj.insert(
351                    "connect_uri".into(),
352                    Value::Text(String::from_utf8_lossy(&r.connect_uri).into_owned()),
353                );
354                r_obj.insert("track_namespace".into(), ns_to_json(&r.track_namespace));
355                r_obj.insert(
356                    "track_name".into(),
357                    Value::Text(String::from_utf8_lossy(&r.track_name).into_owned()),
358                );
359                o.insert("redirect".into(), Value::Map(r_obj));
360            }
361            o
362        }
363        ControlMessage::Subscribe(m) => {
364            let mut o = Map::new();
365            o.insert("request_id".into(), vi(m.request_id.into_inner()));
366            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
367            o.insert(
368                "track_name".into(),
369                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
370            );
371            o.insert("parameters".into(), params_to_json(&m.parameters));
372            o
373        }
374        ControlMessage::SubscribeOk(m) => {
375            let mut o = Map::new();
376            o.insert("track_alias".into(), vi(m.track_alias.into_inner()));
377            o.insert("parameters".into(), params_to_json(&m.parameters));
378            o.insert("track_properties".into(), track_props_to_json(&m.track_properties));
379            o
380        }
381        ControlMessage::RequestUpdate(m) => {
382            let mut o = Map::new();
383            o.insert("request_id".into(), vi(m.request_id.into_inner()));
384            o.insert("parameters".into(), params_to_json(&m.parameters));
385            o
386        }
387        ControlMessage::Publish(m) => {
388            let mut o = Map::new();
389            o.insert("request_id".into(), vi(m.request_id.into_inner()));
390            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
391            o.insert(
392                "track_name".into(),
393                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
394            );
395            o.insert("track_alias".into(), vi(m.track_alias.into_inner()));
396            o.insert("parameters".into(), params_to_json(&m.parameters));
397            o.insert("track_properties".into(), track_props_to_json(&m.track_properties));
398            o
399        }
400        // New in draft-20, and the only control message with a Parameters field
401        // and nothing else: no Request ID, no Track Properties.
402        ControlMessage::PublishStateNotify(m) => {
403            let mut o = Map::new();
404            o.insert("parameters".into(), params_to_json(&m.parameters));
405            o
406        }
407        ControlMessage::PublishDone(m) => {
408            let mut o = Map::new();
409            o.insert("status_code".into(), vi(m.status_code.into_inner()));
410            o.insert("stream_count".into(), vi(m.stream_count.into_inner()));
411            o.insert(
412                "reason_phrase".into(),
413                Value::Text(String::from_utf8_lossy(&m.reason_phrase).into_owned()),
414            );
415            o
416        }
417        ControlMessage::PublishNamespace(m) => {
418            let mut o = Map::new();
419            o.insert("request_id".into(), vi(m.request_id.into_inner()));
420            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
421            o.insert("parameters".into(), params_to_json(&m.parameters));
422            o
423        }
424        ControlMessage::Namespace(m) => {
425            let mut o = Map::new();
426            o.insert("namespace_suffix".into(), ns_to_json(&m.namespace_suffix));
427            o
428        }
429        ControlMessage::NamespaceDone(m) => {
430            let mut o = Map::new();
431            o.insert("namespace_suffix".into(), ns_to_json(&m.namespace_suffix));
432            o
433        }
434        ControlMessage::SubscribeNamespace(m) => {
435            let mut o = Map::new();
436            o.insert("request_id".into(), vi(m.request_id.into_inner()));
437            o.insert("namespace_prefix".into(), ns_to_json(&m.namespace_prefix));
438            o.insert("parameters".into(), params_to_json(&m.parameters));
439            o
440        }
441        ControlMessage::SubscribeTracks(m) => {
442            let mut o = Map::new();
443            o.insert("request_id".into(), vi(m.request_id.into_inner()));
444            o.insert("namespace_prefix".into(), ns_to_json(&m.namespace_prefix));
445            o.insert("parameters".into(), params_to_json(&m.parameters));
446            o
447        }
448        ControlMessage::TrackStatus(m) => {
449            let mut o = Map::new();
450            o.insert("request_id".into(), vi(m.request_id.into_inner()));
451            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
452            o.insert(
453                "track_name".into(),
454                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
455            );
456            o.insert("parameters".into(), params_to_json(&m.parameters));
457            o
458        }
459        // Draft-20's FETCH is byte-identical to SUBSCRIBE apart from its type
460        // code: no Fetch Type, no joining pair, no inline range.
461        ControlMessage::Fetch(m) => {
462            let mut o = Map::new();
463            o.insert("request_id".into(), vi(m.request_id.into_inner()));
464            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
465            o.insert(
466                "track_name".into(),
467                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
468            );
469            o.insert("parameters".into(), params_to_json(&m.parameters));
470            o
471        }
472        ControlMessage::FetchOk(m) => {
473            let mut o = Map::new();
474            o.insert("end_of_track".into(), vi(m.end_of_track as u64));
475            o.insert("end_group".into(), vi(m.end_group.into_inner()));
476            o.insert("end_object".into(), vi(m.end_object.into_inner()));
477            o.insert("parameters".into(), params_to_json(&m.parameters));
478            o.insert("track_properties".into(), track_props_to_json(&m.track_properties));
479            o
480        }
481        ControlMessage::PublishSkipped(m) => {
482            let mut o = Map::new();
483            o.insert("namespace_suffix".into(), ns_to_json(&m.namespace_suffix));
484            o.insert(
485                "track_name".into(),
486                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
487            );
488            o
489        }
490    };
491    obj
492}