1use crate::error::CodecError;
11use crate::kvp::KvpError;
12use crate::varint::{Moqt17, Moqt18, VarInt, VarIntError};
13use crate::version::{DraftVersion, VarIntEncoding};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct PropertyRules {
18 pub varint: VarIntEncoding,
20 pub delta_types: bool,
23 pub max_value_len: Option<u64>,
25 pub immutable_type: Option<u64>,
28}
29
30impl PropertyRules {
31 pub fn for_draft(draft: DraftVersion) -> Option<Self> {
34 let varint = draft.varint_encoding();
35 let rules = |delta_types, max_value_len, immutable_type| {
36 Some(Self { varint, delta_types, max_value_len, immutable_type })
37 };
38 match draft {
39 DraftVersion::Draft07 => None,
40 DraftVersion::Draft08 | DraftVersion::Draft09 | DraftVersion::Draft10 => {
42 rules(false, None, None)
43 }
44 DraftVersion::Draft11 | DraftVersion::Draft12 | DraftVersion::Draft13 => {
46 rules(false, Some(MAX_VALUE_LEN), None)
47 }
48 DraftVersion::Draft14 | DraftVersion::Draft15 => {
51 rules(false, Some(MAX_VALUE_LEN), Some(IMMUTABLE))
52 }
53 DraftVersion::Draft16
58 | DraftVersion::Draft17
59 | DraftVersion::Draft18
60 | DraftVersion::Draft19
61 | DraftVersion::Draft20
62 | DraftVersion::Draft21
63 | DraftVersion::Draft22
64 | DraftVersion::Draft23 => rules(true, Some(MAX_VALUE_LEN), Some(IMMUTABLE)),
65 }
66 }
67}
68
69const MAX_VALUE_LEN: u64 = 0xFFFF;
70const IMMUTABLE: u64 = 0x0B;
71
72#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct ObjectProperty<'a> {
75 pub property_type: u64,
77 pub value: PropertyValue<'a>,
79}
80
81#[derive(Debug, Clone, PartialEq, Eq)]
83pub enum PropertyValue<'a> {
84 Varint(u64),
86 Bytes(&'a [u8]),
88 Immutable {
91 bytes: &'a [u8],
93 properties: Vec<ObjectProperty<'a>>,
95 },
96}
97
98pub fn parse_object_properties(
108 draft: DraftVersion,
109 block: &[u8],
110) -> Result<Vec<ObjectProperty<'_>>, CodecError> {
111 match PropertyRules::for_draft(draft) {
112 Some(rules) => parse_with(&rules, block),
113 None if block.is_empty() => Ok(Vec::new()),
114 None => Err(malformed(0, format!("{draft:?} objects carry no properties"))),
115 }
116}
117
118pub fn parse_with<'a>(
120 rules: &PropertyRules,
121 block: &'a [u8],
122) -> Result<Vec<ObjectProperty<'a>>, CodecError> {
123 read_run(rules, block, 0, false)
124}
125
126fn read_run<'a>(
127 rules: &PropertyRules,
128 run: &'a [u8],
129 base: usize,
130 nested: bool,
131) -> Result<Vec<ObjectProperty<'a>>, CodecError> {
132 let mut properties = Vec::new();
133 let mut cursor = run;
134 let mut prev_type = 0u64;
135 while !cursor.is_empty() {
136 let offset = base + (run.len() - cursor.len());
137 let raw = read_varint(rules, &mut cursor, offset)?;
138 let property_type = if rules.delta_types {
139 prev_type.checked_add(raw).ok_or(CodecError::KeyDeltaOverflow(prev_type, raw))?
140 } else {
141 raw
142 };
143 prev_type = property_type;
144
145 if property_type % 2 == 0 {
146 let value = read_varint(rules, &mut cursor, offset)?;
147 properties.push(ObjectProperty { property_type, value: PropertyValue::Varint(value) });
148 continue;
149 }
150
151 let len = read_varint(rules, &mut cursor, offset)?;
152 if rules.max_value_len.is_some_and(|max| len > max) {
153 return Err(KvpError::ValueTooLong(usize::try_from(len).unwrap_or(usize::MAX)).into());
154 }
155 let Some(bytes) = usize::try_from(len).ok().and_then(|len| cursor.get(..len)) else {
156 return Err(malformed(
157 offset,
158 format!(
159 "property 0x{property_type:02X} claims {len} bytes but the property block has \
160 {} left",
161 cursor.len()
162 ),
163 ));
164 };
165 let value_base = base + (run.len() - cursor.len());
166 cursor = &cursor[bytes.len()..];
167
168 let value = if rules.immutable_type == Some(property_type) {
169 if nested {
170 return Err(malformed(
171 offset,
172 "Immutable Properties inside Immutable Properties".to_string(),
173 ));
174 }
175 PropertyValue::Immutable {
176 bytes,
177 properties: read_run(rules, bytes, value_base, true)?,
178 }
179 } else {
180 PropertyValue::Bytes(bytes)
181 };
182 properties.push(ObjectProperty { property_type, value });
183 }
184 Ok(properties)
185}
186
187fn read_varint(
188 rules: &PropertyRules,
189 cursor: &mut &[u8],
190 offset: usize,
191) -> Result<u64, CodecError> {
192 let decoded = match rules.varint {
193 VarIntEncoding::Rfc9000 => VarInt::decode(cursor),
194 VarIntEncoding::Moqt17 => VarInt::decode_moqt::<Moqt17>(cursor),
195 VarIntEncoding::Moqt18 => VarInt::decode_moqt::<Moqt18>(cursor),
196 };
197 match decoded {
198 Ok(v) => Ok(v.into_inner()),
199 Err(VarIntError::UnexpectedEnd) => {
202 Err(malformed(offset, "a property runs past the end of the property block".to_string()))
203 }
204 Err(e) => Err(e.into()),
205 }
206}
207
208fn malformed(offset: usize, detail: String) -> CodecError {
209 CodecError::MalformedObjectProperties { offset, detail }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 fn rules(draft: DraftVersion) -> PropertyRules {
217 PropertyRules::for_draft(draft).unwrap()
218 }
219
220 #[test]
221 fn reads_odd_even_and_repeated_types_in_wire_order() {
222 let props =
224 parse_with(&rules(DraftVersion::Draft17), &[0x0d, 0x02, 0xca, 0xfe, 0x2f, 0x02])
225 .unwrap();
226 assert_eq!(
227 props,
228 vec![
229 ObjectProperty { property_type: 0x0d, value: PropertyValue::Bytes(&[0xca, 0xfe]) },
230 ObjectProperty { property_type: 0x3c, value: PropertyValue::Varint(2) },
231 ]
232 );
233 let repeated =
234 parse_with(&rules(DraftVersion::Draft17), &[0x0d, 0x01, 0xaa, 0x00, 0x01, 0xbb])
235 .unwrap();
236 assert_eq!(repeated.len(), 2);
237 assert_eq!(repeated[1].property_type, 0x0d);
238 }
239
240 #[test]
241 fn absolute_types_before_draft_16() {
242 let props = parse_with(&rules(DraftVersion::Draft15), &[0x3c, 0x01, 0x3e, 0x02]).unwrap();
243 assert_eq!(props[1].property_type, 0x3e);
244 let delta = parse_with(&rules(DraftVersion::Draft16), &[0x3c, 0x01, 0x02, 0x02]).unwrap();
245 assert_eq!(delta[1].property_type, 0x3e);
246 }
247
248 #[test]
249 fn immutable_contents_restart_their_delta_at_zero() {
250 let props = parse_with(&rules(DraftVersion::Draft17), &[0x0b, 0x02, 0x3c, 0x02]).unwrap();
251 let PropertyValue::Immutable { bytes, properties } = &props[0].value else {
252 panic!("0xB not read as Immutable Properties: {props:?}");
253 };
254 assert_eq!(*bytes, &[0x3c, 0x02]);
255 assert_eq!(properties[0].property_type, 0x3c);
256 let plain = parse_with(&rules(DraftVersion::Draft13), &[0x0b, 0x02, 0x3c, 0x02]).unwrap();
258 assert_eq!(plain[0].value, PropertyValue::Bytes(&[0x3c, 0x02]));
259 }
260
261 #[test]
262 fn refuses_what_the_drafts_call_malformed() {
263 let d17 = rules(DraftVersion::Draft17);
264 assert!(matches!(
266 parse_with(&d17, &[0x0d, 0x05, 0xca]),
267 Err(CodecError::MalformedObjectProperties { offset: 0, .. })
268 ));
269 assert!(matches!(
271 parse_with(&d17, &[0x3c, 0x02, 0x3c]),
272 Err(CodecError::MalformedObjectProperties { offset: 2, .. })
273 ));
274 assert!(matches!(
275 parse_with(&d17, &[0x0b, 0x03, 0x0b, 0x01, 0x00]),
276 Err(CodecError::MalformedObjectProperties { offset: 2, .. })
277 ));
278 let mut too_long = vec![0x0d];
279 VarInt::from_u64_moqt(0x10000).encode_moqt::<Moqt17>(&mut too_long);
280 assert_eq!(
281 parse_with(&d17, &too_long),
282 Err(CodecError::Kvp(KvpError::ValueTooLong(0x10000)))
283 );
284 let mut overflow = vec![0x3c, 0x01];
285 VarInt::from_u64_moqt(u64::MAX).encode_moqt::<Moqt17>(&mut overflow);
286 assert_eq!(parse_with(&d17, &overflow), Err(CodecError::KeyDeltaOverflow(0x3c, u64::MAX)));
287 assert!(matches!(
289 parse_with(&rules(DraftVersion::Draft10), &[0x0d, 0x80, 0x01, 0x00, 0x00]),
290 Err(CodecError::MalformedObjectProperties { .. })
291 ));
292 }
293
294 #[test]
295 fn draft_07_has_no_block() {
296 assert_eq!(parse_object_properties(DraftVersion::Draft07, &[]), Ok(Vec::new()));
297 assert!(parse_object_properties(DraftVersion::Draft07, &[0x02, 0x00]).is_err());
298 }
299}