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
36            entries.push(KeyEntry {
37                key_namespace: header.kind,
38                key_value: decode_utf8_string_with_length(buf, size)?,
39            });
40        }
41
42        Ok(Self { entries })
43    }
44
45    fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<()> {
46        (self.entries.len() as u32).encode(buf)?;
47
48        for entry in &self.entries {
49            let header = Header {
50                kind: entry.key_namespace,
51                size: Some(entry.key_value.len()),
52            };
53            header.encode(buf)?;
54            entry.key_value.as_bytes().encode(buf)?;
55        }
56
57        Ok(())
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn test_keys_roundtrip() {
67        let keys = Keys {
68            entries: vec![
69                KeyEntry {
70                    key_namespace: FourCC::new(b"mdta"),
71                    key_value: "com.apple.quicktime.make".into(),
72                },
73                KeyEntry {
74                    key_namespace: FourCC::new(b"mdta"),
75                    key_value: "com.apple.quicktime.model".into(),
76                },
77            ],
78        };
79
80        let mut buf = Vec::new();
81        keys.encode(&mut buf).unwrap();
82
83        let decoded = Keys::decode(&mut buf.as_slice()).expect("failed to decode keys");
84        assert_eq!(decoded, keys);
85    }
86
87    #[test]
88    fn test_keys_truncated_entry_does_not_panic() {
89        let mut buf = Vec::new();
90        buf.extend_from_slice(&24u32.to_be_bytes()); // outer `keys` box size
91        buf.extend_from_slice(b"keys");
92        buf.extend_from_slice(&0u32.to_be_bytes()); // version + flags
93        buf.extend_from_slice(&1u32.to_be_bytes()); // entry_count = 1
94
95        // Inner entry header declares a body size of 100 bytes, but nothing
96        // actually follows within the outer box.
97        buf.extend_from_slice(&108u32.to_be_bytes());
98        buf.extend_from_slice(b"mdta");
99
100        let result = Keys::decode(&mut buf.as_slice());
101        assert!(result.is_err());
102    }
103
104    #[test]
105    fn test_keys_empty() {
106        let keys = Keys::default();
107
108        let mut buf = Vec::new();
109        keys.encode(&mut buf).unwrap();
110
111        let decoded = Keys::decode(&mut buf.as_slice()).expect("failed to decode keys");
112        assert_eq!(decoded, keys);
113    }
114
115    // A declared per-entry size larger than what's actually left in the box
116    // must be a decode error, not a panic from an out-of-bounds slice.
117    #[test]
118    fn test_keys_truncated_entry() {
119        let mut buf = Vec::new();
120        0u32.encode(&mut buf).unwrap(); // version + flags
121        1u32.encode(&mut buf).unwrap(); // entry_count
122        100u32.encode(&mut buf).unwrap(); // header size (way beyond what follows)
123        FourCC::new(b"mdta").encode(&mut buf).unwrap();
124        buf.extend_from_slice(b"short");
125
126        let err = Keys::decode_body(&mut buf.as_slice()).unwrap_err();
127        assert!(matches!(err, Error::OutOfBounds));
128    }
129
130    // A bogus/huge entry_count with no matching entries must fail cleanly
131    // once the buffer runs out, rather than allocate or loop unboundedly.
132    #[test]
133    fn test_keys_malformed_count() {
134        let mut buf = Vec::new();
135        0u32.encode(&mut buf).unwrap(); // version + flags
136        u32::MAX.encode(&mut buf).unwrap(); // entry_count
137        FourCC::new(b"mdta").encode(&mut buf).unwrap(); // truncated first header
138
139        let err = Keys::decode_body(&mut buf.as_slice()).unwrap_err();
140        assert!(matches!(err, Error::OutOfBounds));
141    }
142}