Skip to main content

moqtap_codec/draft23/
fields.rs

1use crate::draft23::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-23 Message Parameter names, one row per entry in the Message
19// Parameters registry (Section 16.7, Table 14).
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        0x11 => Some("paused"),
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        0x36 => Some("subscribe_tracks_id"),
43        _ => None,
44    }
45}
46
47// Draft-23 setup option names, one row per entry in the Setup Options
48// registry.
49fn d20_option_name(key: u64) -> Option<&'static str> {
50    match key {
51        0x01 => Some("path"),
52        0x03 => Some("authorization_token"),
53        0x04 => Some("max_auth_token_cache_size"),
54        0x05 => Some("authority"),
55        0x06 => Some("max_filter_ranges"),
56        0x07 => Some("moqt_implementation"),
57        0x08 => Some("max_request_updates"),
58        0x09 => Some("max_control_message_size"),
59        _ => None,
60    }
61}
62
63/// Render a draft-23 Range Filter parameter value: SetID (u8), an optional
64/// Property Type (varint, for the Object/Track Property filters), then a
65/// sequence of delta-encoded inclusive Start/End range pairs. A zero-length
66/// value denotes filter removal (only meaningful in REQUEST_UPDATE).
67///
68/// The structure is Section 8.6's.
69///
70/// # The parse belongs to [`crate::range_filter`], not here
71///
72/// Draft-23'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-23 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-23 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.19.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::draft23::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-23 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::draft23::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            // token_value runs to the end of the bytes (no inner length).
198            o.insert("token_value".into(), Value::Bytes(buf.to_vec()));
199        }
200        _ => {
201            if let Ok(tt) = VarInt::decode_moqt::<Wire>(&mut buf) {
202                o.insert("token_type".into(), vi(tt.into_inner()));
203            }
204            o.insert("token_value".into(), Value::Bytes(buf.to_vec()));
205        }
206    }
207    Value::Map(o)
208}
209
210/// Render a LARGEST OBJECT (0x09) parameter value: a Group and an Object, as
211/// two varints.
212///
213/// # What a value it cannot read renders as
214///
215/// The raw bytes, as `fields::params` does. Field extraction runs on a message
216/// that has already decoded, so it has no refusal to give, and it has no
217/// guarantee the two varints are there: nothing between `KeyValuePair::decode`
218/// and here looks at a 0x09 value's contents. An empty value fails the first
219/// read and a single `0x00` fails the second, which is the nastier of the two —
220/// the value looks well formed right up to the point where it is not. Neither
221/// read may panic on it.
222fn decode_largest_object(bytes: &[u8]) -> Value {
223    let mut buf = bytes;
224    let Ok(group) = VarInt::decode_moqt::<Wire>(&mut buf) else {
225        return Value::Bytes(bytes.to_vec());
226    };
227    let Ok(object) = VarInt::decode_moqt::<Wire>(&mut buf) else {
228        return Value::Bytes(bytes.to_vec());
229    };
230    let (group, object) = (group.into_inner(), object.into_inner());
231    let mut obj = Map::new();
232    obj.insert("group".into(), vi(group));
233    obj.insert("object".into(), vi(object));
234    Value::Map(obj)
235}
236
237fn decode_track_namespace_prefix(bytes: &[u8]) -> Value {
238    let mut buf = bytes;
239    match TrackNamespace::decode_allow_empty_moqt::<Wire>(&mut buf) {
240        Ok(ns) => ns_to_json(&ns),
241        Err(_) => Value::Bytes(bytes.to_vec()),
242    }
243}
244
245fn params_to_json(params: &[KeyValuePair]) -> Value {
246    crate::fields::kvp_entries(params, |key, value| {
247        let Some(name) = d20_param_name(key) else {
248            return (None, None);
249        };
250        let rendered = match (value, key) {
251            (KvpValue::Bytes(b), 0x21) => decode_location_filter(b),
252            (KvpValue::Bytes(b), 0x23) => decode_fill_parameters(b),
253            // One arm for all five: which of them carries a Property Type
254            // is a property of the type, and `range_filter` is the one
255            // place that decides it. Two arms passing a bool were two
256            // chances to answer it differently.
257            (KvpValue::Bytes(b), 0x25..=0x29) => decode_range_filter(b, key),
258            (KvpValue::Bytes(b), 0x09) => decode_largest_object(b),
259            (KvpValue::Bytes(b), 0x34) => decode_track_namespace_prefix(b),
260            (KvpValue::Bytes(b), _) if name == "authorization_token" => auth_token_to_json_d20(b),
261            (KvpValue::Varint(v), _) => vi(v.into_inner()),
262            (KvpValue::Bytes(b), _) => Value::Text(String::from_utf8_lossy(b).into_owned()),
263        };
264        (Some(name), Some(rendered))
265    })
266}
267
268pub(crate) fn options_to_json(options: &[KeyValuePair]) -> Value {
269    crate::fields::kvp_entries(options, |key, value| {
270        let Some(name) = d20_option_name(key) else {
271            return (None, None);
272        };
273        let rendered = match value {
274            KvpValue::Varint(v) => vi(v.into_inner()),
275            KvpValue::Bytes(b) if name == "authorization_token" => auth_token_to_json_d20(b),
276            KvpValue::Bytes(b)
277                if key == crate::draft23::message::MAX_CONTROL_MESSAGE_SIZE_OPTION =>
278            {
279                match crate::draft23::message::decode_max_control_message_size(b) {
280                    Ok(size) => vi(size),
281                    Err(_) => Value::Bytes(b.to_vec()),
282                }
283            }
284            KvpValue::Bytes(b) => Value::Text(String::from_utf8_lossy(b).into_owned()),
285        };
286        (Some(name), Some(rendered))
287    })
288}
289
290/// Render a REQUEST_ERROR's Error Details (Section 9.4.2). A REDIRECT whose
291/// value does not parse keeps its bytes.
292fn error_details_to_json(details: &[KeyValuePair]) -> Value {
293    use crate::draft23::message::{error_detail_types as T, Redirect};
294    crate::fields::kvp_entries(details, |key, value| match (key, value) {
295        (T::REDIRECT, KvpValue::Bytes(b)) => {
296            let Ok(r) = Redirect::decode(b) else {
297                return (Some("redirect"), None);
298            };
299            let mut o = Map::new();
300            o.insert(
301                "connect_uri".into(),
302                Value::Text(String::from_utf8_lossy(&r.connect_uri).into_owned()),
303            );
304            o.insert("track_namespace".into(), ns_to_json(&r.track_namespace));
305            o.insert(
306                "track_name".into(),
307                Value::Text(String::from_utf8_lossy(&r.track_name).into_owned()),
308            );
309            (Some("redirect"), Some(Value::Map(o)))
310        }
311        (T::UNSUPPORTED_PROPERTY, KvpValue::Varint(v)) => {
312            (Some("unsupported_property"), Some(vi(v.into_inner())))
313        }
314        _ => (None, None),
315    })
316}
317
318fn d20_track_prop_name(key: u64) -> Option<&'static str> {
319    match key {
320        0x02 => Some("object_delivery_timeout"),
321        0x04 => Some("max_cache_duration"),
322        0x06 => Some("subgroup_delivery_timeout"),
323        0x0b => Some("immutable_properties"),
324        0x0e => Some("default_publisher_priority"),
325        0x22 => Some("default_publisher_group_order"),
326        0x30 => Some("dynamic_groups"),
327        _ => None,
328    }
329}
330
331fn track_props_to_json(props: &[KeyValuePair]) -> Value {
332    crate::fields::kvp_entries(props, |key, value| {
333        let name = d20_track_prop_name(key);
334        let rendered = match value {
335            KvpValue::Varint(v) => Some(vi(v.into_inner())),
336            // Immutable Properties hold "a sequence of Key-Value-Pairs (see
337            // Figure 4) that are themselves Track or Object Properties"
338            // (Section 10.7), rendered as a nested list when they parse.
339            KvpValue::Bytes(b) if key == 0x0b => crate::draft23::message::decode_property_list(b)
340                .ok()
341                .map(|nested| track_props_to_json(&nested)),
342            // A property this draft does not name keeps its bytes rather than
343            // a name invented from its type, which is what an entry's absent
344            // `name` already says.
345            KvpValue::Bytes(_) => None,
346        };
347        (name, rendered)
348    })
349}
350
351/// This draft's field names for a decoded control message.
352///
353/// Keys are the names this draft gives its fields, in the order it defines
354/// them. An optional field the message did not carry is absent rather than
355/// zero.
356pub fn message_fields(msg: &ControlMessage) -> Map {
357    let obj = match msg {
358        ControlMessage::Setup(m) => {
359            let mut o = Map::new();
360            o.insert("options".into(), options_to_json(&m.options));
361            o
362        }
363        ControlMessage::GoAway(m) => {
364            let mut o = Map::new();
365            o.insert(
366                "new_session_uri".into(),
367                Value::Text(String::from_utf8_lossy(&m.new_session_uri).into_owned()),
368            );
369            o.insert("timeout".into(), vi(m.timeout.into_inner()));
370            o
371        }
372        ControlMessage::RequestOk(m) => {
373            let mut o = Map::new();
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::RequestError(m) => {
379            let mut o = Map::new();
380            o.insert("error_code".into(), vi(m.error_code.into_inner()));
381            o.insert("retry_interval".into(), vi(m.retry_interval.into_inner()));
382            o.insert(
383                "reason_phrase".into(),
384                Value::Text(String::from_utf8_lossy(&m.reason_phrase).into_owned()),
385            );
386            o.insert("error_details".into(), error_details_to_json(&m.error_details));
387            o
388        }
389        ControlMessage::Subscribe(m) => {
390            let mut o = Map::new();
391            o.insert("request_id".into(), vi(m.request_id.into_inner()));
392            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
393            o.insert(
394                "track_name".into(),
395                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
396            );
397            o.insert("parameters".into(), params_to_json(&m.parameters));
398            o
399        }
400        ControlMessage::SubscribeOk(m) => {
401            let mut o = Map::new();
402            o.insert("track_alias".into(), vi(m.track_alias.into_inner()));
403            o.insert("parameters".into(), params_to_json(&m.parameters));
404            o.insert("track_properties".into(), track_props_to_json(&m.track_properties));
405            o
406        }
407        ControlMessage::RequestUpdate(m) => {
408            let mut o = Map::new();
409            o.insert("request_id".into(), vi(m.request_id.into_inner()));
410            o.insert("parameters".into(), params_to_json(&m.parameters));
411            o
412        }
413        ControlMessage::Publish(m) => {
414            let mut o = Map::new();
415            o.insert("request_id".into(), vi(m.request_id.into_inner()));
416            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
417            o.insert(
418                "track_name".into(),
419                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
420            );
421            o.insert("track_alias".into(), vi(m.track_alias.into_inner()));
422            o.insert("parameters".into(), params_to_json(&m.parameters));
423            o.insert("track_properties".into(), track_props_to_json(&m.track_properties));
424            o
425        }
426        ControlMessage::PublishStateNotify(m) => {
427            let mut o = Map::new();
428            o.insert("parameters".into(), params_to_json(&m.parameters));
429            o
430        }
431        ControlMessage::PublishDone(m) => {
432            let mut o = Map::new();
433            o.insert("status_code".into(), vi(m.status_code.into_inner()));
434            o.insert("stream_count".into(), vi(m.stream_count.into_inner()));
435            o.insert(
436                "reason_phrase".into(),
437                Value::Text(String::from_utf8_lossy(&m.reason_phrase).into_owned()),
438            );
439            o
440        }
441        ControlMessage::PublishNamespace(m) => {
442            let mut o = Map::new();
443            o.insert("request_id".into(), vi(m.request_id.into_inner()));
444            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
445            o.insert("parameters".into(), params_to_json(&m.parameters));
446            o
447        }
448        ControlMessage::Namespace(m) => {
449            let mut o = Map::new();
450            o.insert("namespace_suffix".into(), ns_to_json(&m.namespace_suffix));
451            o.insert("parameters".into(), params_to_json(&m.parameters));
452            o
453        }
454        ControlMessage::NamespaceDone(m) => {
455            let mut o = Map::new();
456            o.insert("namespace_suffix".into(), ns_to_json(&m.namespace_suffix));
457            o
458        }
459        ControlMessage::SubscribeNamespace(m) => {
460            let mut o = Map::new();
461            o.insert("request_id".into(), vi(m.request_id.into_inner()));
462            o.insert("namespace_prefix".into(), ns_to_json(&m.namespace_prefix));
463            o.insert("parameters".into(), params_to_json(&m.parameters));
464            o
465        }
466        ControlMessage::SubscribeTracks(m) => {
467            let mut o = Map::new();
468            o.insert("request_id".into(), vi(m.request_id.into_inner()));
469            o.insert("namespace_prefix".into(), ns_to_json(&m.namespace_prefix));
470            o.insert("parameters".into(), params_to_json(&m.parameters));
471            o
472        }
473        ControlMessage::TrackStatus(m) => {
474            let mut o = Map::new();
475            o.insert("request_id".into(), vi(m.request_id.into_inner()));
476            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
477            o.insert(
478                "track_name".into(),
479                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
480            );
481            o.insert("parameters".into(), params_to_json(&m.parameters));
482            o
483        }
484        ControlMessage::Fetch(m) => {
485            let mut o = Map::new();
486            o.insert("request_id".into(), vi(m.request_id.into_inner()));
487            o.insert("track_namespace".into(), ns_to_json(&m.track_namespace));
488            o.insert(
489                "track_name".into(),
490                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
491            );
492            o.insert("parameters".into(), params_to_json(&m.parameters));
493            o
494        }
495        ControlMessage::PublishSkipped(m) => {
496            let mut o = Map::new();
497            o.insert("namespace_suffix".into(), ns_to_json(&m.namespace_suffix));
498            o.insert(
499                "track_name".into(),
500                Value::Text(String::from_utf8_lossy(&m.track_name).into_owned()),
501            );
502            o
503        }
504    };
505    obj
506}