Skip to main content

mp4_atom/meta/
keys.rs

1use crate::*;
2
3/// A single entry in a Metadata Item Keys Box ('keys').
4#[derive(Debug, Clone, PartialEq, Eq)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub struct KeyEntry {
7    pub key_namespace: FourCC,
8    pub key_value: String,
9}
10
11/// Metadata Item Keys Box ('keys').
12///
13/// Used alongside a `hdlr` with `handler_type == 'mdta'` (Apple's key-based metadata
14/// scheme): each `ilst` item under this scheme is keyed by a 1-based numeric index
15/// into this table instead of a well-known FourCC, and this box gives that index a
16/// namespaced string key (e.g. `com.apple.quicktime.make`).
17#[derive(Debug, Clone, PartialEq, Eq, Default)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19pub struct Keys {
20    pub entries: Vec<KeyEntry>,
21}
22
23impl AtomExt for Keys {
24    type Ext = ();
25
26    const KIND_EXT: FourCC = FourCC::new(b"keys");
27
28    fn decode_body_ext<B: Buf>(buf: &mut B, _ext: ()) -> Result<Self> {
29        let entry_count = u32::decode(buf)?;
30        let mut entries = Vec::with_capacity((entry_count as usize).min(4096));
31
32        for _ in 0..entry_count {
33            let header = Header::decode(buf)?;
34            let size = header.size.ok_or(Error::InvalidSize)?;
35            if size > buf.remaining() {
36                return Err(Error::OutOfBounds);
37            }
38
39            let key_value = String::from_utf8(buf.slice(size).to_vec())
40                .map_err(|err| Error::InvalidString(err.to_string()))?;
41            buf.advance(size);
42
43            entries.push(KeyEntry {
44                key_namespace: header.kind,
45                key_value,
46            });
47        }
48
49        Ok(Self { entries })
50    }
51
52    fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<()> {
53        (self.entries.len() as u32).encode(buf)?;
54
55        for entry in &self.entries {
56            let header = Header {
57                kind: entry.key_namespace,
58                size: Some(entry.key_value.len()),
59            };
60            header.encode(buf)?;
61            entry.key_value.as_bytes().encode(buf)?;
62        }
63
64        Ok(())
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn test_keys_roundtrip() {
74        let keys = Keys {
75            entries: vec![
76                KeyEntry {
77                    key_namespace: FourCC::new(b"mdta"),
78                    key_value: "com.apple.quicktime.make".into(),
79                },
80                KeyEntry {
81                    key_namespace: FourCC::new(b"mdta"),
82                    key_value: "com.apple.quicktime.model".into(),
83                },
84            ],
85        };
86
87        let mut buf = Vec::new();
88        keys.encode(&mut buf).unwrap();
89
90        let decoded = Keys::decode(&mut buf.as_slice()).expect("failed to decode keys");
91        assert_eq!(decoded, keys);
92    }
93
94    #[test]
95    fn test_keys_truncated_entry_does_not_panic() {
96        let mut buf = Vec::new();
97        buf.extend_from_slice(&24u32.to_be_bytes()); // outer `keys` box size
98        buf.extend_from_slice(b"keys");
99        buf.extend_from_slice(&0u32.to_be_bytes()); // version + flags
100        buf.extend_from_slice(&1u32.to_be_bytes()); // entry_count = 1
101
102        // Inner entry header declares a body size of 100 bytes, but nothing
103        // actually follows within the outer box.
104        buf.extend_from_slice(&108u32.to_be_bytes());
105        buf.extend_from_slice(b"mdta");
106
107        let result = Keys::decode(&mut buf.as_slice());
108        assert!(result.is_err());
109    }
110
111    #[test]
112    fn test_keys_empty() {
113        let keys = Keys::default();
114
115        let mut buf = Vec::new();
116        keys.encode(&mut buf).unwrap();
117
118        let decoded = Keys::decode(&mut buf.as_slice()).expect("failed to decode keys");
119        assert_eq!(decoded, keys);
120    }
121
122    // A declared per-entry size larger than what's actually left in the box
123    // must be a decode error, not a panic from an out-of-bounds slice.
124    #[test]
125    fn test_keys_truncated_entry() {
126        let mut buf = Vec::new();
127        0u32.encode(&mut buf).unwrap(); // version + flags
128        1u32.encode(&mut buf).unwrap(); // entry_count
129        100u32.encode(&mut buf).unwrap(); // header size (way beyond what follows)
130        FourCC::new(b"mdta").encode(&mut buf).unwrap();
131        buf.extend_from_slice(b"short");
132
133        let err = Keys::decode_body(&mut buf.as_slice()).unwrap_err();
134        assert!(matches!(err, Error::OutOfBounds));
135    }
136
137    // A bogus/huge entry_count with no matching entries must fail cleanly
138    // once the buffer runs out, rather than allocate or loop unboundedly.
139    #[test]
140    fn test_keys_malformed_count() {
141        let mut buf = Vec::new();
142        0u32.encode(&mut buf).unwrap(); // version + flags
143        u32::MAX.encode(&mut buf).unwrap(); // entry_count
144        FourCC::new(b"mdta").encode(&mut buf).unwrap(); // truncated first header
145
146        let err = Keys::decode_body(&mut buf.as_slice()).unwrap_err();
147        assert!(matches!(err, Error::OutOfBounds));
148    }
149}