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 self.content_type.clone().unwrap().as_str().encode(buf)?;
70 self.content_encoding
71 .clone()
72 .unwrap_or("".to_string())
73 .as_str()
74 .encode(buf)?;
75 if version == ItemInfoEntryVersion::V1 {
76 return Err(Error::Unsupported("infe version 1 extensions"));
77 }
78 } else {
79 if version == ItemInfoEntryVersion::V2 {
80 (self.item_id as u16).encode(buf)?;
81 } else {
82 self.item_id.encode(buf)?;
83 }
84 self.item_protection_index.encode(buf)?;
85 Some(self.item_type).encode(buf)?;
86 self.item_name.as_str().encode(buf)?;
87 if self.item_type == Some(FourCC::new(b"mime")) {
88 self.content_type.clone().unwrap().as_str().encode(buf)?;
89 self.content_encoding
90 .clone()
91 .unwrap_or("".to_string())
92 .as_str()
93 .encode(buf)?;
94 } else if self.item_type == Some(FourCC::new(b"uri ")) {
95 let item_uri_type = self.item_uri_type.as_ref().ok_or(Error::MissingContent(
96 "item_uri_type required with 'uri ' item_type",
97 ))?;
98 item_uri_type.as_str().encode(buf)?;
99 }
100 }
101 Ok(ItemInfoEntryExt {
102 version,
103 item_not_in_presentation: self.item_not_in_presentation,
104 })
105 }
106
107 fn decode_body_ext<B: Buf>(buf: &mut B, ext: Self::Ext) -> Result<Self> {
109 let item_id: u32;
110 let item_protection_index;
111 let mut item_type = None;
112 let item_name;
113 let mut content_type = None;
114 let mut content_encoding = None;
115 let mut item_uri_type = None;
116 if (ext.version == ItemInfoEntryVersion::V0) || (ext.version == ItemInfoEntryVersion::V1) {
117 item_id = u16::decode(buf)? as u32;
118 item_protection_index = u16::decode(buf)?;
119 item_name = String::decode(buf)?;
120 content_type = Some(String::decode(buf)?);
121 content_encoding = Some(String::decode(buf)?);
122 if ext.version == ItemInfoEntryVersion::V1 {
123 return Err(Error::Unsupported("infe version 1 extensions"));
124 }
125 } else {
126 if ext.version == ItemInfoEntryVersion::V2 {
127 item_id = u16::decode(buf)? as u32;
128 } else {
129 item_id = u32::decode(buf)?;
130 }
131 item_protection_index = u16::decode(buf)?;
132 item_type = Some(FourCC::decode(buf)?);
133 item_name = String::decode(buf)?;
134 if item_type == Some(FourCC::new(b"mime")) {
135 content_type = Some(String::decode(buf)?);
136 content_encoding = Some(String::decode(buf)?);
137 } else if item_type == Some(FourCC::new(b"uri ")) {
138 item_uri_type = Some(String::decode(buf)?);
139 }
140 }
141 Ok(ItemInfoEntry {
142 item_id,
143 item_protection_index,
144 item_type,
145 item_name,
146 content_type,
147 content_encoding,
148 item_uri_type,
149 item_not_in_presentation: ext.item_not_in_presentation,
150 })
151 }
152}
153
154#[derive(Debug, Clone, PartialEq, Eq)]
156#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
157pub struct Iinf {
158 pub item_infos: Vec<ItemInfoEntry>,
160}
161
162impl AtomExt for Iinf {
163 type Ext = IinfExt;
164
165 const KIND_EXT: FourCC = FourCC::new(b"iinf");
166
167 fn decode_body_ext<B: Buf>(buf: &mut B, ext: IinfExt) -> Result<Self> {
169 let mut item_infos = vec![];
170 let entry_count = if ext.version == IinfVersion::V0 {
171 u16::decode(buf)? as usize
172 } else {
173 u32::decode(buf)? as usize
174 };
175 for _ in 0..entry_count {
176 item_infos.push(ItemInfoEntry::decode(buf)?);
177 }
178 Ok(Iinf { item_infos })
179 }
180
181 fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<IinfExt> {
183 let version;
184 if self.item_infos.len() > u16::MAX as usize {
185 version = IinfVersion::V1;
186 (self.item_infos.len() as u32).encode(buf)?
187 } else {
188 version = IinfVersion::V0;
189 (self.item_infos.len() as u16).encode(buf)?
190 }
191 for item_info in &self.item_infos {
192 item_info.encode(buf)?;
193 }
194
195 Ok(IinfExt { version })
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 const ENCODED_IINF_LIBAVIF_MIME: &[u8] = &[
204 0, 0, 0, 65, 105, 105, 110, 102, 0, 0, 0, 0, 0, 1, 0, 0, 0, 51, 105, 110, 102, 101, 2, 0,
205 0, 0, 0, 1, 0, 0, 109, 105, 109, 101, 73, 116, 101, 109, 0, 99, 111, 110, 116, 101, 110,
206 116, 45, 116, 121, 112, 101, 0, 117, 110, 107, 110, 111, 119, 110, 47, 109, 105, 109, 101,
207 0,
208 ];
209
210 #[test]
212 fn test_iinf_libavif_decode_mime() {
213 let buf: &mut std::io::Cursor<&&[u8]> =
214 &mut std::io::Cursor::new(&ENCODED_IINF_LIBAVIF_MIME);
215
216 let iinf: Iinf = Iinf {
217 item_infos: vec![ItemInfoEntry {
218 item_id: 1,
219 item_protection_index: 0,
220 item_type: Some(FourCC::new(b"mime")),
221 item_name: "Item".to_string(),
222 content_type: Some("content-type".to_string()),
223 content_encoding: Some("unknown/mime".to_string()),
224 item_uri_type: None,
225 item_not_in_presentation: false,
226 }],
227 };
228 let decoded = Iinf::decode(buf).unwrap();
229 assert_eq!(decoded, iinf);
230 }
231
232 #[test]
234 fn test_iinf_avif_encode_mime() {
235 let iinf: Iinf = Iinf {
236 item_infos: vec![ItemInfoEntry {
237 item_id: 1,
238 item_protection_index: 0,
239 item_type: Some(FourCC::new(b"mime")),
240 item_name: "Item".to_string(),
241 content_type: Some("content-type".to_string()),
242 content_encoding: Some("unknown/mime".to_string()),
243 item_uri_type: None,
244 item_not_in_presentation: false,
245 }],
246 };
247 let mut buf = Vec::new();
248 iinf.encode(&mut buf).unwrap();
249
250 assert_eq!(buf.as_slice(), ENCODED_IINF_LIBAVIF_MIME);
251 }
252
253 const ENCODED_IINF_LIBAVIF_URI: &[u8] = &[
254 0, 0, 0, 50, 105, 105, 110, 102, 0, 0, 0, 0, 0, 1, 0, 0, 0, 36, 105, 110, 102, 101, 2, 0,
255 0, 0, 0, 1, 0, 0, 117, 114, 105, 32, 73, 116, 101, 109, 0, 117, 114, 105, 58, 47, 47, 116,
256 101, 115, 116, 0,
257 ];
258
259 #[test]
261 fn test_iinf_libavif_decode_uri() {
262 let buf: &mut std::io::Cursor<&&[u8]> =
263 &mut std::io::Cursor::new(&ENCODED_IINF_LIBAVIF_URI);
264
265 let iinf: Iinf = Iinf {
266 item_infos: vec![ItemInfoEntry {
267 item_id: 1,
268 item_protection_index: 0,
269 item_type: Some(FourCC::new(b"uri ")),
270 item_name: "Item".to_string(),
271 content_type: None,
272 content_encoding: None,
273 item_uri_type: Some("uri://test".to_string()),
274 item_not_in_presentation: false,
275 }],
276 };
277 let decoded = Iinf::decode(buf).unwrap();
278 assert_eq!(decoded, iinf);
279 }
280
281 #[test]
283 fn test_iinf_avif_encode_uri() {
284 let iinf: Iinf = Iinf {
285 item_infos: vec![ItemInfoEntry {
286 item_id: 1,
287 item_protection_index: 0,
288 item_type: Some(FourCC::new(b"uri ")),
289 item_name: "Item".to_string(),
290 content_type: None,
291 content_encoding: None,
292 item_uri_type: Some("uri://test".to_string()),
293 item_not_in_presentation: false,
294 }],
295 };
296 let mut buf = Vec::new();
297 iinf.encode(&mut buf).unwrap();
298
299 assert_eq!(buf.as_slice(), ENCODED_IINF_LIBAVIF_URI);
300 }
301
302 #[test]
304 fn test_iinf_avif_encode_uri_invalid() {
305 let iinf: Iinf = Iinf {
306 item_infos: vec![ItemInfoEntry {
307 item_id: 1,
308 item_protection_index: 0,
309 item_type: Some(FourCC::new(b"uri ")),
310 item_name: "Item".to_string(),
311 content_type: None,
312 content_encoding: None,
313 item_uri_type: None, item_not_in_presentation: false,
315 }],
316 };
317 let mut buf = Vec::new();
318 assert!(matches!(
319 iinf.encode(&mut buf),
320 Err(Error::MissingContent(_))
321 ));
322 }
323
324 #[test]
326 fn test_iinf_decode_unsupported_infe_v1_returns_error() {
327 let body: &[u8] = &[
328 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x13, b'i', b'n', b'f', b'e', 0x01, 0x00, 0x00, 0x00,
332 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
335 ];
336 let result = Iinf::decode_body(&mut std::io::Cursor::new(body));
337
338 assert!(matches!(
339 result,
340 Err(Error::Unsupported("infe version 1 extensions"))
341 ));
342
343 let fuzz_body: &[u8] = &[
344 0x00, 0x00, 0x00, 0x00, 0x5b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x80,
345 0x01, 0x00, 0x00, 0x04, 0x00, b'p', b'y', b't', b'f',
346 ];
347 let fuzz_result = Iinf::decode_body(&mut std::io::Cursor::new(fuzz_body));
348
349 assert!(matches!(
350 fuzz_result,
351 Err(Error::Unsupported("infe version 1 extensions"))
352 ));
353 }
354}