Skip to main content

moqtap_codec/draft22/
fields.rs

1use crate::draft22::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-22 Message Parameter names, one row per entry in the Message
19// Parameters registry (Section 16.7).
20fn d20_param_name(key: u64) -> Option<&'static str> {
21    match key {
22        0x02 => Some("object_delivery_timeout"),
23        0x03 => Some("authorization_token"),
24        0x04 => Some("rendezvous_timeout"),
25        0x06 => Some("subgroup_delivery_timeout"),
26        0x08 => Some("expires"),
27        0x09 => Some("largest_object"),
28        0x0A => Some("fill_timeout"),
29        0x10 => Some("forward"),
30        0x20 => Some("subscriber_priority"),
31        0x21 => Some("location_filter"),
32        0x22 => Some("group_order"),
33        0x23 => Some("fill_parameters"),
34        0x25 => Some("subgroup_filter"),
35        0x26 => Some("objectid_filter"),
36        0x27 => Some("priority_filter"),
37        0x28 => Some("object_property_filter"),
38        0x29 => Some("track_property_filter"),
39        0x32 => Some("new_group_request"),
40        0x34 => Some("track_namespace_prefix"),
41        0x35 => Some("include_properties"),
42        _ => None,
43    }
44}
45
46// Draft-22 setup option names, one row per entry in the Setup Options
47// registry.
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-22 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 8.6 and changed no codepoint,
68/// field order or delta arithmetic.
69///
70/// # The parse belongs to [`crate::range_filter`], not here
71///
72/// Draft-22'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 second parse
76/// written out here would have to repeat both guards the value needs: a varint
77/// read that unwraps panics where `Buf::has_remaining` promises one more byte
78/// and a MoQT varint needs nine, and a bare `+` on a delta baseline wraps in
79/// release, recording a filter as `start = u64::MAX, end = 0` — a wrong answer
80/// 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-22 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-22 LOCATION_FILTER value.
141///
142/// `filter_type` is always present, and it is the key that carries the
143/// meaning: types 0x00 (no filter) and 0x05 (Next Object) both carry no fields
144/// and differ only there (Section 9.20.9, Table 6). The field names are
145/// positional — the type names how many follow, and they are always the first
146/// that many of `StartGroup`, `StartObject`, `EndGroupDelta`, `EndObject` — so
147/// a key is present exactly when its field is on the wire.
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 (filter_type, fields) = crate::draft22::message::decode_location_filter(bytes)
154        .expect("the decoder accepted this parameter, so its value parses");
155    let mut obj = Map::new();
156    obj.insert("filter_type".into(), vi(filter_type));
157    for (name, value) in NAMES.iter().zip(fields) {
158        obj.insert((*name).to_string(), vi(value));
159    }
160    Value::Map(obj)
161}
162
163/// Decode a draft-22 FILL_PARAMETERS value: a nested parameter block in its own
164/// scope, reported the same way an outer block is.
165///
166/// The nesting is what makes this recursive with [`params_to_json`] — a fill's
167/// LOCATION_FILTER is rendered exactly as the subscription's is — and it
168/// bottoms out because Table 7 does not list FILL_PARAMETERS inside itself.
169fn decode_fill_parameters(bytes: &[u8]) -> Value {
170    let nested = crate::draft22::message::decode_fill_parameters(bytes)
171        .expect("the decoder accepted this parameter, so its value parses");
172    params_to_json(&nested)
173}
174
175fn auth_token_to_json_d20(bytes: &[u8]) -> Value {
176    let mut buf = bytes;
177    let alias_type = match VarInt::decode_moqt::<Wire>(&mut buf) {
178        Ok(v) => v,
179        Err(_) => return Value::Bytes(bytes.to_vec()),
180    };
181    let at = alias_type.into_inner();
182    let mut o = Map::new();
183    o.insert("alias_type".into(), vi(at));
184    match at {
185        0 | 2 => {
186            if let Ok(ta) = VarInt::decode_moqt::<Wire>(&mut buf) {
187                o.insert("token_alias".into(), vi(ta.into_inner()));
188            }
189        }
190        1 => {
191            if let Ok(ta) = VarInt::decode_moqt::<Wire>(&mut buf) {
192                o.insert("token_alias".into(), vi(ta.into_inner()));
193            }
194            if let Ok(tt) = VarInt::decode_moqt::<Wire>(&mut buf) {
195                o.insert("token_type".into(), vi(tt.into_inner()));
196            }
197            // Draft-18 onwards: token_value runs to end of bytes (no inner
198            // length).
199            o.insert("token_value".into(), Value::Bytes(buf.to_vec()));
200        }
201        _ => {
202            if let Ok(tt) = VarInt::decode_moqt::<Wire>(&mut buf) {
203                o.insert("token_type".into(), vi(tt.into_inner()));
204            }
205            o.insert("token_value".into(), Value::Bytes(buf.to_vec()));
206        }
207    }
208    Value::Map(o)
209}
210
211/// Render a LARGEST OBJECT (0x09) parameter value: a Group and an Object, as
212/// two varints.
213///
214/// # What a value it cannot read renders as
215///
216/// The raw bytes, as `fields::params` does. Field extraction runs on a message
217/// that has already decoded, so it has no refusal to give, and it has no
218/// guarantee the two varints are there: nothing between `KeyValuePair::decode`
219/// and here looks at a 0x09 value's contents. An empty value fails the first
220/// read and a single `0x00` fails the second, which is the nastier of the two —
221/// the value looks well formed right up to the point where it is not. Neither
222/// read may panic on it.
223fn decode_largest_object(bytes: &[u8]) -> Value {
224    let mut buf = bytes;
225    let Ok(group) = VarInt::decode_moqt::<Wire>(&mut buf) else {
226        return Value::Bytes(bytes.to_vec());
227    };
228    let Ok(object) = VarInt::decode_moqt::<Wire>(&mut buf) else {
229        return Value::Bytes(bytes.to_vec());
230    };
231    let (group, object) = (group.into_inner(), object.into_inner());
232    let mut obj = Map::new();
233    obj.insert("group".into(), vi(group));
234    obj.insert("object".into(), vi(object));
235    Value::Map(obj)
236}
237
238fn decode_track_namespace_prefix(bytes: &[u8]) -> Value {
239    let mut buf = bytes;
240    match TrackNamespace::decode_allow_empty_moqt::<Wire>(&mut buf) {
241        Ok(ns) => ns_to_json(&ns),
242        Err(_) => Value::Bytes(bytes.to_vec()),
243    }
244}
245
246fn params_to_json(params: &[KeyValuePair]) -> Value {
247    crate::fields::kvp_entries(params, |key, value| {
248        let Some(name) = d20_param_name(key) else {
249            return (None, None);
250        };
251        let rendered = match (value, key) {
252            (KvpValue::Bytes(b), 0x21) => decode_location_filter(b),
253            (KvpValue::Bytes(b), 0x23) => decode_fill_parameters(b),
254            // One arm for all five: which of them carries a Property Type
255            // is a property of the type, and `range_filter` is the one
256            // place that decides it. Two arms passing a bool were two
257            // chances to answer it differently.
258            (KvpValue::Bytes(b), 0x25..=0x29) => decode_range_filter(b, key),
259            (KvpValue::Bytes(b), 0x09) => decode_largest_object(b),
260            (KvpValue::Bytes(b), 0x34) => decode_track_namespace_prefix(b),
261            (KvpValue::Bytes(b), _) if name == "authorization_token" => auth_token_to_json_d20(b),
262            (KvpValue::Varint(v), _) => vi(v.into_inner()),
263            (KvpValue::Bytes(b), _) => Value::Text(String::from_utf8_lossy(b).into_owned()),
264        };
265        (Some(name), Some(rendered))
266    })
267}
268
269pub(crate) fn options_to_json(options: &[KeyValuePair]) -> Value {
270    crate::fields::kvp_entries(options, |key, value| {
271        let Some(name) = d20_option_name(key) else {
272            return (None, None);
273        };
274        let rendered = match value {
275            KvpValue::Varint(v) => vi(v.into_inner()),
276            KvpValue::Bytes(b) if name == "authorization_token" => auth_token_to_json_d20(b),
277            KvpValue::Bytes(b) => Value::Text(String::from_utf8_lossy(b).into_owned()),
278        };
279        (Some(name), Some(rendered))
280    })
281}
282
283fn d20_track_prop_name(key: u64) -> Option<&'static str> {
284    match key {
285        0x02 => Some("object_delivery_timeout"),
286        0x04 => Some("max_cache_duration"),
287        0x06 => Some("subgroup_delivery_timeout"),
288        0x0b => Some("immutable_properties"),
289        0x0e => Some("default_publisher_priority"),
290        0x22 => Some("default_publisher_group_order"),
291        0x30 => Some("dynamic_groups"),
292        _ => None,
293    }
294}
295
296fn track_props_to_json(props: &[KeyValuePair]) -> Value {
297    crate::fields::kvp_entries(props, |key, value| {
298        let name = d20_track_prop_name(key);
299        let rendered = match value {
300            KvpValue::Varint(v) => Some(vi(v.into_inner())),
301            // A property this draft does not name keeps its bytes rather than
302            // a name invented from its type, which is what an entry's absent
303            // `name` already says.
304            KvpValue::Bytes(_) => None,
305        };
306        (name, rendered)
307    })
308}
309
310/// This draft's field names for a decoded control message.
311///
312/// Keys are the names this draft gives its fields, in the order it defines
313/// them. An optional field the message did not carry is absent rather than
314/// zero.
315pub fn message_fields(msg: &ControlMessage) -> Map {
316    let obj = match msg {
317        ControlMessage::Setup(m) => {
318            let mut o = Map::new();
319            o.insert("options".into(), options_to_json(&m.options));
320            o
321        }
322        ControlMessage::GoAway(m) => {
323            let mut o = Map::new();
324            o.insert(
325                "new_session_uri".into(),
326                Value::Text(String::from_utf8_lossy(&m.new_session_uri).into_owned()),
327            );
328            o.insert("timeout".into(), vi(m.timeout.into_inner()));
329            o
330        }
331        ControlMessage::RequestOk(m) => {
332            let mut o = Map::new();
333            o.insert("parameters".into(), params_to_json(&m.parameters));
334            o.insert("track_properties".into(), track_props_to_json(&m.track_properties));
335            o
336        }
337        ControlMessage::RequestError(m) => {
338            let mut o = Map::new();
339            o.insert("error_code".into(), vi(m.error_code.into_inner()));
340            o.insert("retry_interval".into(), vi(m.retry_interval.into_inner()));
341            o.insert(
342                "reason_phrase".into(),
343                Value::Text(String::from_utf8_lossy(&m.reason_phrase).into_owned()),
344            );
345            if let Some(r) = &m.redirect {
346                let mut r_obj = Map::new();
347                r_obj.insert(
348                    "connect_uri".into(),
349                    Value::Text(String::from_utf8_lossy(&r.connect_uri).into_owned()),
350                );
351                r_obj.insert("track_namespace".into(), ns_to_json(&r.track_namespace));
352                r_obj.insert(
353                    "track_name".into(),
354                    Value::Text(String::from_utf8_lossy(&r.track_name).into_owned()),
355                );
356                o.insert("redirect".into(), Value::Map(r_obj));
357            }
358            o
359        }
360        ControlMessage::Subscribe(m) => {
361            let mut o = Map::new();
362            o.insert("request_id".into(), vi(m.request_id.into_inner()));
363            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
364            o.insert(
365                "track_name".into(),
366                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
367            );
368            o.insert("parameters".into(), params_to_json(&m.parameters));
369            o
370        }
371        ControlMessage::SubscribeOk(m) => {
372            let mut o = Map::new();
373            o.insert("track_alias".into(), vi(m.track_alias.into_inner()));
374            o.insert("parameters".into(), params_to_json(&m.parameters));
375            o.insert("track_properties".into(), track_props_to_json(&m.track_properties));
376            o
377        }
378        ControlMessage::RequestUpdate(m) => {
379            let mut o = Map::new();
380            o.insert("request_id".into(), vi(m.request_id.into_inner()));
381            o.insert("parameters".into(), params_to_json(&m.parameters));
382            o
383        }
384        ControlMessage::Publish(m) => {
385            let mut o = Map::new();
386            o.insert("request_id".into(), vi(m.request_id.into_inner()));
387            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
388            o.insert(
389                "track_name".into(),
390                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
391            );
392            o.insert("track_alias".into(), vi(m.track_alias.into_inner()));
393            o.insert("parameters".into(), params_to_json(&m.parameters));
394            o.insert("track_properties".into(), track_props_to_json(&m.track_properties));
395            o
396        }
397        // New in draft-20, and the only control message with a Parameters field
398        // and nothing else: no Request ID, no Track Properties.
399        ControlMessage::PublishStateNotify(m) => {
400            let mut o = Map::new();
401            o.insert("parameters".into(), params_to_json(&m.parameters));
402            o
403        }
404        ControlMessage::PublishDone(m) => {
405            let mut o = Map::new();
406            o.insert("status_code".into(), vi(m.status_code.into_inner()));
407            o.insert("stream_count".into(), vi(m.stream_count.into_inner()));
408            o.insert(
409                "reason_phrase".into(),
410                Value::Text(String::from_utf8_lossy(&m.reason_phrase).into_owned()),
411            );
412            o
413        }
414        ControlMessage::PublishNamespace(m) => {
415            let mut o = Map::new();
416            o.insert("request_id".into(), vi(m.request_id.into_inner()));
417            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
418            o.insert("parameters".into(), params_to_json(&m.parameters));
419            o
420        }
421        ControlMessage::Namespace(m) => {
422            let mut o = Map::new();
423            o.insert("namespace_suffix".into(), ns_to_json(&m.namespace_suffix));
424            o
425        }
426        ControlMessage::NamespaceDone(m) => {
427            let mut o = Map::new();
428            o.insert("namespace_suffix".into(), ns_to_json(&m.namespace_suffix));
429            o
430        }
431        ControlMessage::SubscribeNamespace(m) => {
432            let mut o = Map::new();
433            o.insert("request_id".into(), vi(m.request_id.into_inner()));
434            o.insert("namespace_prefix".into(), ns_to_json(&m.namespace_prefix));
435            o.insert("parameters".into(), params_to_json(&m.parameters));
436            o
437        }
438        ControlMessage::SubscribeTracks(m) => {
439            let mut o = Map::new();
440            o.insert("request_id".into(), vi(m.request_id.into_inner()));
441            o.insert("namespace_prefix".into(), ns_to_json(&m.namespace_prefix));
442            o.insert("parameters".into(), params_to_json(&m.parameters));
443            o
444        }
445        ControlMessage::TrackStatus(m) => {
446            let mut o = Map::new();
447            o.insert("request_id".into(), vi(m.request_id.into_inner()));
448            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
449            o.insert(
450                "track_name".into(),
451                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
452            );
453            o.insert("parameters".into(), params_to_json(&m.parameters));
454            o
455        }
456        // Draft-22's FETCH is byte-identical to SUBSCRIBE apart from its type
457        // code: no Fetch Type, no joining pair, no inline range.
458        ControlMessage::Fetch(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::FetchOk(m) => {
470            let mut o = Map::new();
471            o.insert("end_of_track".into(), vi(m.end_of_track as u64));
472            o.insert("end_group".into(), vi(m.end_group.into_inner()));
473            o.insert("end_object".into(), vi(m.end_object.into_inner()));
474            o.insert("parameters".into(), params_to_json(&m.parameters));
475            o.insert("track_properties".into(), track_props_to_json(&m.track_properties));
476            o
477        }
478        ControlMessage::PublishSkipped(m) => {
479            let mut o = Map::new();
480            o.insert("namespace_suffix".into(), ns_to_json(&m.namespace_suffix));
481            o.insert(
482                "track_name".into(),
483                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
484            );
485            o
486        }
487    };
488    obj
489}