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    let mut obj = Map::new();
211    let mut unknown = Vec::new();
212
213    for p in params {
214        let key = p.key.into_inner();
215        if let Some(name) = d19_param_name(key) {
216            match (&p.value, key) {
217                (KvpValue::Bytes(b), 0x21) => {
218                    obj.insert(name.to_string(), decode_location_filter(b));
219                }
220                // One arm for all five: which of them carries a Property Type
221                // is a property of the type, and `range_filter` is the one
222                // place that decides it. Two arms passing a bool were two
223                // chances to answer it differently.
224                (KvpValue::Bytes(b), 0x25..=0x29) => {
225                    obj.insert(name.to_string(), decode_range_filter(b, key));
226                }
227                (KvpValue::Bytes(b), 0x09) => {
228                    obj.insert(name.to_string(), decode_largest_object(b));
229                }
230                (KvpValue::Bytes(b), 0x34) => {
231                    obj.insert(name.to_string(), decode_track_namespace_prefix(b));
232                }
233                (KvpValue::Bytes(b), _) if name == "authorization_token" => {
234                    obj.insert(name.to_string(), auth_token_to_json_d19(b));
235                }
236                (KvpValue::Varint(v), _) => {
237                    obj.insert(name.to_string(), vi(v.into_inner()));
238                }
239                (KvpValue::Bytes(b), _) => {
240                    obj.insert(
241                        name.to_string(),
242                        Value::Text(String::from_utf8_lossy(b).into_owned()),
243                    );
244                }
245            }
246        } else {
247            let mut entry = Map::new();
248            entry.insert("id".to_string(), Value::Text(format!("0x{:x}", key)));
249            match &p.value {
250                KvpValue::Varint(v) => {
251                    entry.insert("length".to_string(), vi(v.into_inner()));
252                }
253                KvpValue::Bytes(b) => {
254                    entry.insert("length".to_string(), vi(b.len() as u64));
255                    entry.insert("raw_hex".to_string(), Value::Bytes(b.to_vec()));
256                }
257            }
258            unknown.push(Value::Map(entry));
259        }
260    }
261
262    if !unknown.is_empty() {
263        obj.insert("unknown".to_string(), Value::Array(unknown));
264    }
265
266    Value::Map(obj)
267}
268
269fn options_to_json(options: &[KeyValuePair]) -> Value {
270    let mut obj = Map::new();
271    for p in options {
272        let key = p.key.into_inner();
273        if let Some(name) = d19_option_name(key) {
274            match &p.value {
275                KvpValue::Varint(v) => {
276                    obj.insert(name.to_string(), vi(v.into_inner()));
277                }
278                KvpValue::Bytes(b) if name == "authorization_token" => {
279                    obj.insert(name.to_string(), auth_token_to_json_d19(b));
280                }
281                KvpValue::Bytes(b) => {
282                    obj.insert(
283                        name.to_string(),
284                        Value::Text(String::from_utf8_lossy(b).into_owned()),
285                    );
286                }
287            }
288        }
289    }
290    Value::Map(obj)
291}
292
293fn d19_track_prop_name(key: u64) -> Option<&'static str> {
294    match key {
295        0x02 => Some("object_delivery_timeout"),
296        0x04 => Some("max_cache_duration"),
297        0x06 => Some("subgroup_delivery_timeout"),
298        0x0b => Some("immutable_properties"),
299        0x0e => Some("default_publisher_priority"),
300        0x22 => Some("default_publisher_group_order"),
301        0x30 => Some("dynamic_groups"),
302        _ => None,
303    }
304}
305
306fn track_props_to_json(props: &[KeyValuePair]) -> Value {
307    let mut obj = Map::new();
308    for p in props {
309        let key = p.key.into_inner();
310        let name = d19_track_prop_name(key)
311            .map(|s| s.to_string())
312            .unwrap_or_else(|| format!("0x{:x}", key));
313        match &p.value {
314            KvpValue::Varint(v) => {
315                obj.insert(name, vi(v.into_inner()));
316            }
317            KvpValue::Bytes(b) => {
318                obj.insert(name, Value::Bytes(b.to_vec()));
319            }
320        }
321    }
322    Value::Map(obj)
323}
324
325/// This draft's field names for a decoded control message.
326///
327/// Keys are the names this draft gives its fields, in the order it defines
328/// them. An optional field the message did not carry is absent rather than
329/// zero.
330pub fn message_fields(msg: &ControlMessage) -> Map {
331    let obj = match msg {
332        ControlMessage::Setup(m) => {
333            let mut o = Map::new();
334            o.insert("options".into(), options_to_json(&m.options));
335            o
336        }
337        ControlMessage::GoAway(m) => {
338            let mut o = Map::new();
339            o.insert(
340                "new_session_uri".into(),
341                Value::Text(String::from_utf8_lossy(&m.new_session_uri).into_owned()),
342            );
343            o.insert("timeout".into(), vi(m.timeout.into_inner()));
344            o
345        }
346        ControlMessage::RequestOk(m) => {
347            let mut o = Map::new();
348            o.insert("parameters".into(), params_to_json(&m.parameters));
349            o.insert("track_properties".into(), track_props_to_json(&m.track_properties));
350            o
351        }
352        ControlMessage::RequestError(m) => {
353            let mut o = Map::new();
354            o.insert("error_code".into(), vi(m.error_code.into_inner()));
355            o.insert("retry_interval".into(), vi(m.retry_interval.into_inner()));
356            o.insert(
357                "reason_phrase".into(),
358                Value::Text(String::from_utf8_lossy(&m.reason_phrase).into_owned()),
359            );
360            if let Some(r) = &m.redirect {
361                let mut r_obj = Map::new();
362                r_obj.insert(
363                    "connect_uri".into(),
364                    Value::Text(String::from_utf8_lossy(&r.connect_uri).into_owned()),
365                );
366                r_obj.insert("track_namespace".into(), ns_to_json(&r.track_namespace));
367                r_obj.insert(
368                    "track_name".into(),
369                    Value::Text(String::from_utf8_lossy(&r.track_name).into_owned()),
370                );
371                o.insert("redirect".into(), Value::Map(r_obj));
372            }
373            o
374        }
375        ControlMessage::Subscribe(m) => {
376            let mut o = Map::new();
377            o.insert("request_id".into(), vi(m.request_id.into_inner()));
378            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
379            o.insert(
380                "track_name".into(),
381                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
382            );
383            o.insert("parameters".into(), params_to_json(&m.parameters));
384            o
385        }
386        ControlMessage::SubscribeOk(m) => {
387            let mut o = Map::new();
388            o.insert("track_alias".into(), vi(m.track_alias.into_inner()));
389            o.insert("parameters".into(), params_to_json(&m.parameters));
390            o.insert("track_properties".into(), track_props_to_json(&m.track_properties));
391            o
392        }
393        ControlMessage::RequestUpdate(m) => {
394            let mut o = Map::new();
395            o.insert("request_id".into(), vi(m.request_id.into_inner()));
396            o.insert("parameters".into(), params_to_json(&m.parameters));
397            o
398        }
399        ControlMessage::Publish(m) => {
400            let mut o = Map::new();
401            o.insert("request_id".into(), vi(m.request_id.into_inner()));
402            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
403            o.insert(
404                "track_name".into(),
405                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
406            );
407            o.insert("track_alias".into(), vi(m.track_alias.into_inner()));
408            o.insert("parameters".into(), params_to_json(&m.parameters));
409            o.insert("track_properties".into(), track_props_to_json(&m.track_properties));
410            o
411        }
412        ControlMessage::PublishDone(m) => {
413            let mut o = Map::new();
414            o.insert("status_code".into(), vi(m.status_code.into_inner()));
415            o.insert("stream_count".into(), vi(m.stream_count.into_inner()));
416            o.insert(
417                "reason_phrase".into(),
418                Value::Text(String::from_utf8_lossy(&m.reason_phrase).into_owned()),
419            );
420            o
421        }
422        ControlMessage::PublishNamespace(m) => {
423            let mut o = Map::new();
424            o.insert("request_id".into(), vi(m.request_id.into_inner()));
425            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
426            o.insert("parameters".into(), params_to_json(&m.parameters));
427            o
428        }
429        ControlMessage::Namespace(m) => {
430            let mut o = Map::new();
431            o.insert("namespace_suffix".into(), ns_to_json(&m.namespace_suffix));
432            o
433        }
434        ControlMessage::NamespaceDone(m) => {
435            let mut o = Map::new();
436            o.insert("namespace_suffix".into(), ns_to_json(&m.namespace_suffix));
437            o
438        }
439        ControlMessage::SubscribeNamespace(m) => {
440            let mut o = Map::new();
441            o.insert("request_id".into(), vi(m.request_id.into_inner()));
442            o.insert("namespace_prefix".into(), ns_to_json(&m.namespace_prefix));
443            o.insert("parameters".into(), params_to_json(&m.parameters));
444            o
445        }
446        ControlMessage::SubscribeTracks(m) => {
447            let mut o = Map::new();
448            o.insert("request_id".into(), vi(m.request_id.into_inner()));
449            o.insert("namespace_prefix".into(), ns_to_json(&m.namespace_prefix));
450            o.insert("parameters".into(), params_to_json(&m.parameters));
451            o
452        }
453        ControlMessage::TrackStatus(m) => {
454            let mut o = Map::new();
455            o.insert("request_id".into(), vi(m.request_id.into_inner()));
456            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
457            o.insert(
458                "track_name".into(),
459                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
460            );
461            o.insert("parameters".into(), params_to_json(&m.parameters));
462            o
463        }
464        ControlMessage::Fetch(m) => {
465            let mut o = Map::new();
466            o.insert("request_id".into(), vi(m.request_id.into_inner()));
467            o.insert("fetch_type".into(), vi(m.fetch_type as u64));
468            match &m.fetch_payload {
469                crate::draft19::message::FetchPayload::Standalone {
470                    track_namespace,
471                    track_name,
472                    start_group,
473                    start_object,
474                    end_group,
475                    end_object,
476                } => {
477                    o.insert("track_namespace".into(), ns_to_json(track_namespace));
478                    o.insert(
479                        "track_name".into(),
480                        Value::Text(String::from_utf8_lossy(track_name).into_owned()),
481                    );
482                    o.insert("start_group".into(), vi(start_group.into_inner()));
483                    o.insert("start_object".into(), vi(start_object.into_inner()));
484                    o.insert("end_group".into(), vi(end_group.into_inner()));
485                    o.insert("end_object".into(), vi(end_object.into_inner()));
486                }
487                crate::draft19::message::FetchPayload::Joining {
488                    joining_request_id,
489                    joining_start,
490                } => {
491                    o.insert("joining_request_id".into(), vi(joining_request_id.into_inner()));
492                    o.insert("joining_start".into(), vi(joining_start.into_inner()));
493                }
494            }
495            o.insert("parameters".into(), params_to_json(&m.parameters));
496            o
497        }
498        ControlMessage::FetchOk(m) => {
499            let mut o = Map::new();
500            o.insert("end_of_track".into(), vi(m.end_of_track as u64));
501            o.insert("end_group".into(), vi(m.end_group.into_inner()));
502            o.insert("end_object".into(), vi(m.end_object.into_inner()));
503            o.insert("parameters".into(), params_to_json(&m.parameters));
504            o.insert("track_properties".into(), track_props_to_json(&m.track_properties));
505            o
506        }
507        ControlMessage::PublishSkipped(m) => {
508            let mut o = Map::new();
509            o.insert("namespace_suffix".into(), ns_to_json(&m.namespace_suffix));
510            o.insert(
511                "track_name".into(),
512                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
513            );
514            o
515        }
516    };
517    obj
518}