Skip to main content

moqtap_codec/draft19/
fields.rs

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