1use crate::*;
2
3ext! {
7 name: Iinf,
8 versions: [0, 1],
9 flags: {}
10}
11
12ext! {
13 name: ItemInfoEntry,
14 versions: [0, 1, 2, 3],
15 flags: {item_not_in_presentation = 0,}
16}
17
18#[derive(Debug, Clone, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub struct ItemInfoEntry {
22 pub item_id: u32,
24
25 pub item_protection_index: u16,
27
28 pub item_type: Option<FourCC>,
30
31 pub item_name: String,
33
34 pub content_type: Option<String>,
36
37 pub content_encoding: Option<String>,
39
40 pub item_uri_type: Option<String>,
42
43 pub item_not_in_presentation: bool,
45}
46
47impl AtomExt for ItemInfoEntry {
48 const KIND_EXT: FourCC = FourCC::new(b"infe");
49
50 type Ext = ItemInfoEntryExt;
51
52 fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<Self::Ext> {
54 let version: ItemInfoEntryVersion = if self.item_id > u16::MAX as u32 {
56 ItemInfoEntryVersion::V3
57 } else {
58 if self.item_type.is_some() {
60 ItemInfoEntryVersion::V2
61 } else {
62 ItemInfoEntryVersion::V0
63 }
64 };
65 if (version == ItemInfoEntryVersion::V0) || (version == ItemInfoEntryVersion::V1) {
66 (self.item_id as u16).encode(buf)?;
67 self.item_protection_index.encode(buf)?;
68 self.item_name.as_str().encode(buf)?;
69 let content_type = self.content_type.as_deref().ok_or(Error::MissingContent(
70 "content_type required for infe version 0 or 1",
71 ))?;
72 content_type.encode(buf)?;
73 self.content_encoding.as_deref().unwrap_or("").encode(buf)?;
74 if version == ItemInfoEntryVersion::V1 {
75 return Err(Error::Unsupported("infe version 1 extensions"));
76 }
77 } else {
78 if version == ItemInfoEntryVersion::V2 {
79 (self.item_id as u16).encode(buf)?;
80 } else {
81 self.item_id.encode(buf)?;
82 }
83 self.item_protection_index.encode(buf)?;
84 Some(self.item_type).encode(buf)?;
85 self.item_name.as_str().encode(buf)?;
86 if self.item_type == Some(FourCC::new(b"mime")) {
87 let content_type = self.content_type.as_deref().ok_or(Error::MissingContent(
88 "content_type required with 'mime' item_type",
89 ))?;
90 content_type.encode(buf)?;
91 self.content_encoding.as_deref().unwrap_or("").encode(buf)?;
92 } else if self.item_type == Some(FourCC::new(b"uri ")) {
93 let item_uri_type = self.item_uri_type.as_ref().ok_or(Error::MissingContent(
94 "item_uri_type required with 'uri ' item_type",
95 ))?;
96 item_uri_type.as_str().encode(buf)?;
97 }
98 }
99 Ok(ItemInfoEntryExt {
100 version,
101 item_not_in_presentation: self.item_not_in_presentation,
102 })
103 }
104
105 fn decode_body_ext<B: Buf>(buf: &mut B, ext: Self::Ext) -> Result<Self> {
107 let item_id: u32;
108 let item_protection_index;
109 let mut item_type = None;
110 let item_name;
111 let mut content_type = None;
112 let mut content_encoding = None;
113 let mut item_uri_type = None;
114 if (ext.version == ItemInfoEntryVersion::V0) || (ext.version == ItemInfoEntryVersion::V1) {
115 item_id = u16::decode(buf)? as u32;
116 item_protection_index = u16::decode(buf)?;
117 item_name = String::decode(buf)?;
118 content_type = Some(String::decode(buf)?);
119 content_encoding = Some(String::decode(buf)?);
120 if ext.version == ItemInfoEntryVersion::V1 {
121 return Err(Error::Unsupported("infe version 1 extensions"));
122 }
123 } else {
124 if ext.version == ItemInfoEntryVersion::V2 {
125 item_id = u16::decode(buf)? as u32;
126 } else {
127 item_id = u32::decode(buf)?;
128 }
129 item_protection_index = u16::decode(buf)?;
130 item_type = Some(FourCC::decode(buf)?);
131 item_name = String::decode(buf)?;
132 if item_type == Some(FourCC::new(b"mime")) {
133 content_type = Some(String::decode(buf)?);
134 content_encoding = Some(String::decode(buf)?);
135 } else if item_type == Some(FourCC::new(b"uri ")) {
136 item_uri_type = Some(String::decode(buf)?);
137 }
138 }
139 Ok(ItemInfoEntry {
140 item_id,
141 item_protection_index,
142 item_type,
143 item_name,
144 content_type,
145 content_encoding,
146 item_uri_type,
147 item_not_in_presentation: ext.item_not_in_presentation,
148 })
149 }
150}
151
152#[derive(Debug, Clone, PartialEq, Eq)]
154#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
155pub struct Iinf {
156 pub item_infos: Vec<ItemInfoEntry>,
158}
159
160impl AtomExt for Iinf {
161 type Ext = IinfExt;
162
163 const KIND_EXT: FourCC = FourCC::new(b"iinf");
164
165 fn decode_body_ext<B: Buf>(buf: &mut B, ext: IinfExt) -> Result<Self> {
167 let mut item_infos = vec![];
168 let entry_count = if ext.version == IinfVersion::V0 {
169 u16::decode(buf)? as usize
170 } else {
171 u32::decode(buf)? as usize
172 };
173 for _ in 0..entry_count {
174 item_infos.push(ItemInfoEntry::decode(buf)?);
175 }
176 Ok(Iinf { item_infos })
177 }
178
179 fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<IinfExt> {
181 let version;
182 if self.item_infos.len() > u16::MAX as usize {
183 version = IinfVersion::V1;
184 (self.item_infos.len() as u32).encode(buf)?
185 } else {
186 version = IinfVersion::V0;
187 (self.item_infos.len() as u16).encode(buf)?
188 }
189 for item_info in &self.item_infos {
190 item_info.encode(buf)?;
191 }
192
193 Ok(IinfExt { version })
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 const ENCODED_IINF_LIBAVIF_MIME: &[u8] = &[
202 0, 0, 0, 65, 105, 105, 110, 102, 0, 0, 0, 0, 0, 1, 0, 0, 0, 51, 105, 110, 102, 101, 2, 0,
203 0, 0, 0, 1, 0, 0, 109, 105, 109, 101, 73, 116, 101, 109, 0, 99, 111, 110, 116, 101, 110,
204 116, 45, 116, 121, 112, 101, 0, 117, 110, 107, 110, 111, 119, 110, 47, 109, 105, 109, 101,
205 0,
206 ];
207
208 #[test]
210 fn test_iinf_libavif_decode_mime() {
211 let buf: &mut std::io::Cursor<&&[u8]> =
212 &mut std::io::Cursor::new(&ENCODED_IINF_LIBAVIF_MIME);
213
214 let iinf: Iinf = Iinf {
215 item_infos: vec![ItemInfoEntry {
216 item_id: 1,
217 item_protection_index: 0,
218 item_type: Some(FourCC::new(b"mime")),
219 item_name: "Item".to_string(),
220 content_type: Some("content-type".to_string()),
221 content_encoding: Some("unknown/mime".to_string()),
222 item_uri_type: None,
223 item_not_in_presentation: false,
224 }],
225 };
226 let decoded = Iinf::decode(buf).unwrap();
227 assert_eq!(decoded, iinf);
228 }
229
230 #[test]
232 fn test_iinf_avif_encode_mime() {
233 let iinf: Iinf = Iinf {
234 item_infos: vec![ItemInfoEntry {
235 item_id: 1,
236 item_protection_index: 0,
237 item_type: Some(FourCC::new(b"mime")),
238 item_name: "Item".to_string(),
239 content_type: Some("content-type".to_string()),
240 content_encoding: Some("unknown/mime".to_string()),
241 item_uri_type: None,
242 item_not_in_presentation: false,
243 }],
244 };
245 let mut buf = Vec::new();
246 iinf.encode(&mut buf).unwrap();
247
248 assert_eq!(buf.as_slice(), ENCODED_IINF_LIBAVIF_MIME);
249 }
250
251 const ENCODED_IINF_LIBAVIF_URI: &[u8] = &[
252 0, 0, 0, 50, 105, 105, 110, 102, 0, 0, 0, 0, 0, 1, 0, 0, 0, 36, 105, 110, 102, 101, 2, 0,
253 0, 0, 0, 1, 0, 0, 117, 114, 105, 32, 73, 116, 101, 109, 0, 117, 114, 105, 58, 47, 47, 116,
254 101, 115, 116, 0,
255 ];
256
257 const ENCODED_IINF_LIBAVIF_URI_V3: &[u8] = &[
258 0, 0, 0, 67, b'i', b'i', b'n', b'f', 0, 0, 0, 0, 0, 1, 0, 0, 0, 53, b'i', b'n', b'f', b'e',
259 3, 0, 0, 0, 0, 1, 0, 0, 0, 0, b'm', b'i', b'm', b'e', b'I', b't', b'e', b'm', 0, b'c',
260 b'o', b'n', b't', b'e', b'n', b't', b'-', b't', b'y', b'p', b'e', 0, b'u', b'n', b'k',
261 b'n', b'o', b'w', b'n', b'/', b'm', b'i', b'm', b'e', 0,
262 ];
263
264 #[test]
266 fn test_iinf_libavif_decode_mime_v3() {
267 let buf: &mut std::io::Cursor<&&[u8]> =
268 &mut std::io::Cursor::new(&ENCODED_IINF_LIBAVIF_URI_V3);
269
270 let iinf: Iinf = Iinf {
271 item_infos: vec![ItemInfoEntry {
272 item_id: 65536,
273 item_protection_index: 0,
274 item_type: Some(FourCC::new(b"mime")),
275 item_name: "Item".to_string(),
276 content_type: Some("content-type".to_string()),
277 content_encoding: Some("unknown/mime".to_string()),
278 item_uri_type: None,
279 item_not_in_presentation: false,
280 }],
281 };
282 let decoded = Iinf::decode(buf).unwrap();
283 assert_eq!(decoded, iinf);
284 }
285
286 #[test]
288 fn test_iinf_avif_encode_mime_v3() {
289 let iinf: Iinf = Iinf {
290 item_infos: vec![ItemInfoEntry {
291 item_id: 65536,
292 item_protection_index: 0,
293 item_type: Some(FourCC::new(b"mime")),
294 item_name: "Item".to_string(),
295 content_type: Some("content-type".to_string()),
296 content_encoding: Some("unknown/mime".to_string()),
297 item_uri_type: None,
298 item_not_in_presentation: false,
299 }],
300 };
301 let mut buf = Vec::new();
302 iinf.encode(&mut buf).unwrap();
303
304 assert_eq!(buf.as_slice(), ENCODED_IINF_LIBAVIF_URI_V3);
305 }
306
307 #[test]
309 fn test_iinf_libavif_decode_uri() {
310 let buf: &mut std::io::Cursor<&&[u8]> =
311 &mut std::io::Cursor::new(&ENCODED_IINF_LIBAVIF_URI);
312
313 let iinf: Iinf = Iinf {
314 item_infos: vec![ItemInfoEntry {
315 item_id: 1,
316 item_protection_index: 0,
317 item_type: Some(FourCC::new(b"uri ")),
318 item_name: "Item".to_string(),
319 content_type: None,
320 content_encoding: None,
321 item_uri_type: Some("uri://test".to_string()),
322 item_not_in_presentation: false,
323 }],
324 };
325 let decoded = Iinf::decode(buf).unwrap();
326 assert_eq!(decoded, iinf);
327 }
328
329 #[test]
331 fn test_iinf_avif_encode_uri() {
332 let iinf: Iinf = Iinf {
333 item_infos: vec![ItemInfoEntry {
334 item_id: 1,
335 item_protection_index: 0,
336 item_type: Some(FourCC::new(b"uri ")),
337 item_name: "Item".to_string(),
338 content_type: None,
339 content_encoding: None,
340 item_uri_type: Some("uri://test".to_string()),
341 item_not_in_presentation: false,
342 }],
343 };
344 let mut buf = Vec::new();
345 iinf.encode(&mut buf).unwrap();
346
347 assert_eq!(buf.as_slice(), ENCODED_IINF_LIBAVIF_URI);
348 }
349
350 #[test]
352 fn test_iinf_avif_encode_uri_invalid() {
353 let iinf: Iinf = Iinf {
354 item_infos: vec![ItemInfoEntry {
355 item_id: 1,
356 item_protection_index: 0,
357 item_type: Some(FourCC::new(b"uri ")),
358 item_name: "Item".to_string(),
359 content_type: None,
360 content_encoding: None,
361 item_uri_type: None, item_not_in_presentation: false,
363 }],
364 };
365 let mut buf = Vec::new();
366 assert!(matches!(
367 iinf.encode(&mut buf),
368 Err(Error::MissingContent(_))
369 ));
370 }
371
372 #[test]
374 fn test_iinf_encode_v0_without_content_type_returns_error() {
375 let iinf = Iinf {
376 item_infos: vec![ItemInfoEntry {
377 item_id: 1,
378 item_protection_index: 0,
379 item_type: None,
380 item_name: "Item".to_string(),
381 content_type: None,
382 content_encoding: None,
383 item_uri_type: None,
384 item_not_in_presentation: false,
385 }],
386 };
387 let mut buf = Vec::new();
388
389 assert!(matches!(
390 iinf.encode(&mut buf),
391 Err(Error::MissingContent(_))
392 ));
393 }
394
395 #[test]
397 fn test_iinf_encode_mime_without_content_type_returns_error() {
398 let iinf = Iinf {
399 item_infos: vec![ItemInfoEntry {
400 item_id: 1,
401 item_protection_index: 0,
402 item_type: Some(FourCC::new(b"mime")),
403 item_name: "Item".to_string(),
404 content_type: None,
405 content_encoding: None,
406 item_uri_type: None,
407 item_not_in_presentation: false,
408 }],
409 };
410 let mut buf = Vec::new();
411
412 assert!(matches!(
413 iinf.encode(&mut buf),
414 Err(Error::MissingContent(_))
415 ));
416 }
417
418 #[test]
420 fn test_iinf_decode_unsupported_infe_v1_returns_error() {
421 let body: &[u8] = &[
422 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x13, b'i', b'n', b'f', b'e', 0x01, 0x00, 0x00, 0x00,
426 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
429 ];
430 let result = Iinf::decode_body(&mut std::io::Cursor::new(body));
431
432 assert!(matches!(
433 result,
434 Err(Error::Unsupported("infe version 1 extensions"))
435 ));
436
437 let fuzz_body: &[u8] = &[
438 0x00, 0x00, 0x00, 0x00, 0x5b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x80,
439 0x01, 0x00, 0x00, 0x04, 0x00, b'p', b'y', b't', b'f',
440 ];
441 let fuzz_result = Iinf::decode_body(&mut std::io::Cursor::new(fuzz_body));
442
443 assert!(matches!(
444 fuzz_result,
445 Err(Error::UnexpectedBox(kind)) if kind == FourCC::new(b"\0\0A\x80")
446 ));
447 }
448}