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
131fn decode_location_filter(bytes: &[u8]) -> Value {
132    let mut buf = bytes;
133    let filter_type = VarInt::decode_moqt::<Wire>(&mut buf).unwrap().into_inner();
134    let mut obj = Map::new();
135    obj.insert("filter_type".into(), vi(filter_type));
136    match filter_type {
137        3 => {
138            let start_group = VarInt::decode_moqt::<Wire>(&mut buf).unwrap().into_inner();
139            let start_object = VarInt::decode_moqt::<Wire>(&mut buf).unwrap().into_inner();
140            obj.insert("start_group".into(), vi(start_group));
141            obj.insert("start_object".into(), vi(start_object));
142        }
143        4 => {
144            let start_group = VarInt::decode_moqt::<Wire>(&mut buf).unwrap().into_inner();
145            let start_object = VarInt::decode_moqt::<Wire>(&mut buf).unwrap().into_inner();
146            let end_group = VarInt::decode_moqt::<Wire>(&mut buf).unwrap().into_inner();
147            obj.insert("start_group".into(), vi(start_group));
148            obj.insert("start_object".into(), vi(start_object));
149            obj.insert("end_group".into(), vi(end_group));
150        }
151        _ => {}
152    }
153    Value::Map(obj)
154}
155
156fn auth_token_to_json_d19(bytes: &[u8]) -> Value {
157    let mut buf = bytes;
158    let alias_type = match VarInt::decode_moqt::<Wire>(&mut buf) {
159        Ok(v) => v,
160        Err(_) => return Value::Bytes(bytes.to_vec()),
161    };
162    let at = alias_type.into_inner();
163    let mut o = Map::new();
164    o.insert("alias_type".into(), vi(at));
165    match at {
166        0 | 2 => {
167            if let Ok(ta) = VarInt::decode_moqt::<Wire>(&mut buf) {
168                o.insert("token_alias".into(), vi(ta.into_inner()));
169            }
170        }
171        1 => {
172            if let Ok(ta) = VarInt::decode_moqt::<Wire>(&mut buf) {
173                o.insert("token_alias".into(), vi(ta.into_inner()));
174            }
175            if let Ok(tt) = VarInt::decode_moqt::<Wire>(&mut buf) {
176                o.insert("token_type".into(), vi(tt.into_inner()));
177            }
178            // Draft-18: token_value runs to end of bytes (no inner length).
179            o.insert("token_value".into(), Value::Bytes(buf.to_vec()));
180        }
181        _ => {
182            if let Ok(tt) = VarInt::decode_moqt::<Wire>(&mut buf) {
183                o.insert("token_type".into(), vi(tt.into_inner()));
184            }
185            o.insert("token_value".into(), Value::Bytes(buf.to_vec()));
186        }
187    }
188    Value::Map(o)
189}
190
191fn decode_largest_object(bytes: &[u8]) -> Value {
192    let mut buf = bytes;
193    let group = VarInt::decode_moqt::<Wire>(&mut buf).unwrap().into_inner();
194    let object = VarInt::decode_moqt::<Wire>(&mut buf).unwrap().into_inner();
195    let mut obj = Map::new();
196    obj.insert("group".into(), vi(group));
197    obj.insert("object".into(), vi(object));
198    Value::Map(obj)
199}
200
201fn decode_track_namespace_prefix(bytes: &[u8]) -> Value {
202    let mut buf = bytes;
203    match TrackNamespace::decode_allow_empty_moqt::<Wire>(&mut buf) {
204        Ok(ns) => ns_to_json(&ns),
205        Err(_) => Value::Bytes(bytes.to_vec()),
206    }
207}
208
209fn params_to_json(params: &[KeyValuePair]) -> Value {
210    crate::fields::kvp_entries(params, |key, value| {
211        let Some(name) = d19_param_name(key) else {
212            return (None, None);
213        };
214        let rendered = match (value, key) {
215            (KvpValue::Bytes(b), 0x21) => decode_location_filter(b),
216            // One arm for all five: which of them carries a Property Type
217            // is a property of the type, and `range_filter` is the one
218            // place that decides it. Two arms passing a bool were two
219            // chances to answer it differently.
220            (KvpValue::Bytes(b), 0x25..=0x29) => decode_range_filter(b, key),
221            (KvpValue::Bytes(b), 0x09) => decode_largest_object(b),
222            (KvpValue::Bytes(b), 0x34) => decode_track_namespace_prefix(b),
223            (KvpValue::Bytes(b), _) if name == "authorization_token" => auth_token_to_json_d19(b),
224            (KvpValue::Varint(v), _) => vi(v.into_inner()),
225            (KvpValue::Bytes(b), _) => Value::Text(String::from_utf8_lossy(b).into_owned()),
226        };
227        (Some(name), Some(rendered))
228    })
229}
230
231fn options_to_json(options: &[KeyValuePair]) -> Value {
232    crate::fields::kvp_entries(options, |key, value| {
233        let Some(name) = d19_option_name(key) else {
234            return (None, None);
235        };
236        let rendered = match value {
237            KvpValue::Varint(v) => vi(v.into_inner()),
238            KvpValue::Bytes(b) if name == "authorization_token" => auth_token_to_json_d19(b),
239            KvpValue::Bytes(b) => Value::Text(String::from_utf8_lossy(b).into_owned()),
240        };
241        (Some(name), Some(rendered))
242    })
243}
244
245fn d19_track_prop_name(key: u64) -> Option<&'static str> {
246    match key {
247        0x02 => Some("object_delivery_timeout"),
248        0x04 => Some("max_cache_duration"),
249        0x06 => Some("subgroup_delivery_timeout"),
250        0x0b => Some("immutable_properties"),
251        0x0e => Some("default_publisher_priority"),
252        0x22 => Some("default_publisher_group_order"),
253        0x30 => Some("dynamic_groups"),
254        _ => None,
255    }
256}
257
258fn track_props_to_json(props: &[KeyValuePair]) -> Value {
259    crate::fields::kvp_entries(props, |key, value| {
260        let name = d19_track_prop_name(key);
261        let rendered = match value {
262            KvpValue::Varint(v) => Some(vi(v.into_inner())),
263            // A property this draft does not name keeps its bytes rather than
264            // a name invented from its type, which is what an entry's absent
265            // `name` already says.
266            KvpValue::Bytes(_) => None,
267        };
268        (name, rendered)
269    })
270}
271
272/// This draft's field names for a decoded control message.
273///
274/// Keys are the names this draft gives its fields, in the order it defines
275/// them. An optional field the message did not carry is absent rather than
276/// zero.
277pub fn message_fields(msg: &ControlMessage) -> Map {
278    let obj = match msg {
279        ControlMessage::Setup(m) => {
280            let mut o = Map::new();
281            o.insert("options".into(), options_to_json(&m.options));
282            o
283        }
284        ControlMessage::GoAway(m) => {
285            let mut o = Map::new();
286            o.insert(
287                "new_session_uri".into(),
288                Value::Text(String::from_utf8_lossy(&m.new_session_uri).into_owned()),
289            );
290            o.insert("timeout".into(), vi(m.timeout.into_inner()));
291            o
292        }
293        ControlMessage::RequestOk(m) => {
294            let mut o = Map::new();
295            o.insert("parameters".into(), params_to_json(&m.parameters));
296            o.insert("track_properties".into(), track_props_to_json(&m.track_properties));
297            o
298        }
299        ControlMessage::RequestError(m) => {
300            let mut o = Map::new();
301            o.insert("error_code".into(), vi(m.error_code.into_inner()));
302            o.insert("retry_interval".into(), vi(m.retry_interval.into_inner()));
303            o.insert(
304                "reason_phrase".into(),
305                Value::Text(String::from_utf8_lossy(&m.reason_phrase).into_owned()),
306            );
307            if let Some(r) = &m.redirect {
308                let mut r_obj = Map::new();
309                r_obj.insert(
310                    "connect_uri".into(),
311                    Value::Text(String::from_utf8_lossy(&r.connect_uri).into_owned()),
312                );
313                r_obj.insert("track_namespace".into(), ns_to_json(&r.track_namespace));
314                r_obj.insert(
315                    "track_name".into(),
316                    Value::Text(String::from_utf8_lossy(&r.track_name).into_owned()),
317                );
318                o.insert("redirect".into(), Value::Map(r_obj));
319            }
320            o
321        }
322        ControlMessage::Subscribe(m) => {
323            let mut o = Map::new();
324            o.insert("request_id".into(), vi(m.request_id.into_inner()));
325            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
326            o.insert(
327                "track_name".into(),
328                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
329            );
330            o.insert("parameters".into(), params_to_json(&m.parameters));
331            o
332        }
333        ControlMessage::SubscribeOk(m) => {
334            let mut o = Map::new();
335            o.insert("track_alias".into(), vi(m.track_alias.into_inner()));
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::RequestUpdate(m) => {
341            let mut o = Map::new();
342            o.insert("request_id".into(), vi(m.request_id.into_inner()));
343            o.insert("parameters".into(), params_to_json(&m.parameters));
344            o
345        }
346        ControlMessage::Publish(m) => {
347            let mut o = Map::new();
348            o.insert("request_id".into(), vi(m.request_id.into_inner()));
349            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
350            o.insert(
351                "track_name".into(),
352                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
353            );
354            o.insert("track_alias".into(), vi(m.track_alias.into_inner()));
355            o.insert("parameters".into(), params_to_json(&m.parameters));
356            o.insert("track_properties".into(), track_props_to_json(&m.track_properties));
357            o
358        }
359        ControlMessage::PublishDone(m) => {
360            let mut o = Map::new();
361            o.insert("status_code".into(), vi(m.status_code.into_inner()));
362            o.insert("stream_count".into(), vi(m.stream_count.into_inner()));
363            o.insert(
364                "reason_phrase".into(),
365                Value::Text(String::from_utf8_lossy(&m.reason_phrase).into_owned()),
366            );
367            o
368        }
369        ControlMessage::PublishNamespace(m) => {
370            let mut o = Map::new();
371            o.insert("request_id".into(), vi(m.request_id.into_inner()));
372            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
373            o.insert("parameters".into(), params_to_json(&m.parameters));
374            o
375        }
376        ControlMessage::Namespace(m) => {
377            let mut o = Map::new();
378            o.insert("namespace_suffix".into(), ns_to_json(&m.namespace_suffix));
379            o
380        }
381        ControlMessage::NamespaceDone(m) => {
382            let mut o = Map::new();
383            o.insert("namespace_suffix".into(), ns_to_json(&m.namespace_suffix));
384            o
385        }
386        ControlMessage::SubscribeNamespace(m) => {
387            let mut o = Map::new();
388            o.insert("request_id".into(), vi(m.request_id.into_inner()));
389            o.insert("namespace_prefix".into(), ns_to_json(&m.namespace_prefix));
390            o.insert("parameters".into(), params_to_json(&m.parameters));
391            o
392        }
393        ControlMessage::SubscribeTracks(m) => {
394            let mut o = Map::new();
395            o.insert("request_id".into(), vi(m.request_id.into_inner()));
396            o.insert("namespace_prefix".into(), ns_to_json(&m.namespace_prefix));
397            o.insert("parameters".into(), params_to_json(&m.parameters));
398            o
399        }
400        ControlMessage::TrackStatus(m) => {
401            let mut o = Map::new();
402            o.insert("request_id".into(), vi(m.request_id.into_inner()));
403            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
404            o.insert(
405                "track_name".into(),
406                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
407            );
408            o.insert("parameters".into(), params_to_json(&m.parameters));
409            o
410        }
411        ControlMessage::Fetch(m) => {
412            let mut o = Map::new();
413            o.insert("request_id".into(), vi(m.request_id.into_inner()));
414            o.insert("fetch_type".into(), vi(m.fetch_type as u64));
415            match &m.fetch_payload {
416                crate::draft19::message::FetchPayload::Standalone {
417                    track_namespace,
418                    track_name,
419                    start_group,
420                    start_object,
421                    end_group,
422                    end_object,
423                } => {
424                    o.insert("track_namespace".into(), ns_to_json(track_namespace));
425                    o.insert(
426                        "track_name".into(),
427                        Value::Text(String::from_utf8_lossy(track_name).into_owned()),
428                    );
429                    o.insert("start_group".into(), vi(start_group.into_inner()));
430                    o.insert("start_object".into(), vi(start_object.into_inner()));
431                    o.insert("end_group".into(), vi(end_group.into_inner()));
432                    o.insert("end_object".into(), vi(end_object.into_inner()));
433                }
434                crate::draft19::message::FetchPayload::Joining {
435                    joining_request_id,
436                    joining_start,
437                } => {
438                    o.insert("joining_request_id".into(), vi(joining_request_id.into_inner()));
439                    o.insert("joining_start".into(), vi(joining_start.into_inner()));
440                }
441            }
442            o.insert("parameters".into(), params_to_json(&m.parameters));
443            o
444        }
445        ControlMessage::FetchOk(m) => {
446            let mut o = Map::new();
447            o.insert("end_of_track".into(), vi(m.end_of_track as u64));
448            o.insert("end_group".into(), vi(m.end_group.into_inner()));
449            o.insert("end_object".into(), vi(m.end_object.into_inner()));
450            o.insert("parameters".into(), params_to_json(&m.parameters));
451            o.insert("track_properties".into(), track_props_to_json(&m.track_properties));
452            o
453        }
454        ControlMessage::PublishSkipped(m) => {
455            let mut o = Map::new();
456            o.insert("namespace_suffix".into(), ns_to_json(&m.namespace_suffix));
457            o.insert(
458                "track_name".into(),
459                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
460            );
461            o
462        }
463    };
464    obj
465}