Skip to main content

lumen/chunks/
extd.rs

1//! The `EXTD` chunk: vendor and future-standard extensions
2//! ([`spec/10-scene-chunks.md`] section 4.12).
3
4use crate::check::Check;
5use crate::container::CHUNK_FLAG_ENCRYPTED;
6use crate::error::{Error, Result};
7use crate::io::{Reader, Writer};
8
9/// The fixed part of the extension payload: `ext_version` and `ext_type`.
10pub const EXTD_FRAME_LEN: usize = 8;
11
12/// Where the `vendor_id` field sits in the descriptor flags.
13///
14/// `vendor_id` is a registered label, not a dispatch key: the registry lives in
15/// the specification (section 4.12.1) and this crate never consults it. A value
16/// it does not know is an extension its caller does not implement, which the
17/// `critical` bit decides what to do about.
18pub const VENDOR_ID_SHIFT: u32 = 8;
19/// The `vendor_id` field's mask.
20pub const VENDOR_ID_MASK: u32 = 0xFFFF << VENDOR_ID_SHIFT;
21/// `vendor_id` `0x0000` marks an ORA standard extension.
22pub const VENDOR_ID_ORA: u16 = 0x0000;
23/// The `critical` bit: an unimplemented critical extension makes a file unprintable.
24pub const CRITICAL_BIT: u32 = 1 << 24;
25/// Reserved flag bits: 0-3, 5-7 and 25-31.
26pub const EXTD_RESERVED_MASK: u32 = (0xFF & !crate::container::CHUNK_FLAG_ENCRYPTED) | (0x7F << 25);
27
28/// One `EXTD` chunk.
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct Extension {
31    /// Extension format version.
32    pub ext_version: u32,
33    /// Four-character ASCII type code.
34    pub ext_type: [u8; 4],
35    /// Vendor identifier, `0` for an ORA standard extension.
36    pub vendor_id: u16,
37    /// Whether a reader that does not implement this extension must refuse the file.
38    pub critical: bool,
39    /// Whether the payload is sealed.
40    pub encrypted: bool,
41    /// The extension's own payload.
42    pub data: Vec<u8>,
43}
44
45impl Extension {
46    /// Parse a payload and its descriptor flags.
47    pub fn parse(payload: &[u8], flags: u32) -> Result<Extension> {
48        let mut r = Reader::checked(payload, Check::ExtdFrame);
49        let ext_version = r.u32()?;
50        let ext_type = r.array::<4>()?;
51        if !ext_type.is_ascii() {
52            return Err(Error::new(
53                Check::ExtdExtType,
54                format!("ext_type {ext_type:02x?} is not four ASCII characters"),
55            ));
56        }
57        if flags & EXTD_RESERVED_MASK != 0 {
58            return Err(Error::new(
59                Check::ExtdFlags,
60                format!("extension flags {flags:#010x} set a reserved bit"),
61            ));
62        }
63        Ok(Extension {
64            ext_version,
65            ext_type,
66            vendor_id: ((flags & VENDOR_ID_MASK) >> VENDOR_ID_SHIFT) as u16,
67            critical: flags & CRITICAL_BIT != 0,
68            encrypted: flags & CHUNK_FLAG_ENCRYPTED != 0,
69            data: r.rest().to_vec(),
70        })
71    }
72
73    /// Serialize the payload.
74    pub fn to_bytes(&self) -> Vec<u8> {
75        let mut w = Writer::with_capacity(EXTD_FRAME_LEN + self.data.len());
76        w.u32(self.ext_version);
77        w.bytes(&self.ext_type);
78        w.bytes(&self.data);
79        w.into_vec()
80    }
81
82    /// The descriptor flags a writer sets for this extension.
83    pub fn to_flags(&self) -> u32 {
84        let mut flags = u32::from(self.vendor_id) << VENDOR_ID_SHIFT;
85        if self.critical {
86            flags |= CRITICAL_BIT;
87        }
88        if self.encrypted {
89            flags |= CHUNK_FLAG_ENCRYPTED;
90        }
91        flags
92    }
93
94    /// The type code as text.
95    pub fn tag(&self) -> String {
96        String::from_utf8_lossy(&self.ext_type).into_owned()
97    }
98
99    /// Whether this crate implements the extension.
100    ///
101    /// It implements none: an extension's payload is vendor-defined and is
102    /// copied through, never interpreted (section 4.12). A downstream reader
103    /// that does implement one overrides this.
104    pub fn is_implemented(&self) -> bool {
105        false
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn extension_flags_round_trip() {
115        let flags = (0x00ABu32 << VENDOR_ID_SHIFT) | CRITICAL_BIT | CHUNK_FLAG_ENCRYPTED;
116        let mut payload = Vec::new();
117        payload.extend_from_slice(&1u32.to_le_bytes());
118        payload.extend_from_slice(b"SIGN");
119        payload.extend_from_slice(&[9, 8, 7]);
120        let ext = Extension::parse(&payload, flags).unwrap();
121        assert_eq!(ext.ext_version, 1);
122        assert_eq!(&ext.ext_type, b"SIGN");
123        assert_eq!(ext.vendor_id, 0x00AB);
124        assert!(ext.critical);
125        assert!(ext.encrypted);
126        assert_eq!(ext.data, vec![9, 8, 7]);
127        assert_eq!(ext.to_flags(), flags);
128        assert_eq!(ext.tag(), "SIGN");
129
130        let reparsed = Extension::parse(&ext.to_bytes(), ext.to_flags()).unwrap();
131        assert_eq!(reparsed, ext);
132    }
133
134    #[test]
135    fn extension_rejects_bad_frames_types_and_flags() {
136        assert_eq!(
137            Extension::parse(&[1, 2, 3], 0).unwrap_err().check(),
138            Check::ExtdFrame
139        );
140        let non_ascii = [1u32.to_le_bytes(), [0xC3, 0xA9, 0x00, 0x00]].concat();
141        assert_eq!(
142            Extension::parse(&non_ascii, 0).unwrap_err().check(),
143            Check::ExtdExtType
144        );
145        let payload = [1u32.to_le_bytes(), *b"TEST"].concat();
146        assert_eq!(
147            Extension::parse(&payload, 1 << 0).unwrap_err().check(),
148            Check::ExtdFlags
149        );
150        assert_eq!(
151            Extension::parse(&payload, 1 << 25).unwrap_err().check(),
152            Check::ExtdFlags
153        );
154        // The ENCRYPTED bit is not reserved.
155        assert!(Extension::parse(&payload, CHUNK_FLAG_ENCRYPTED).is_ok());
156    }
157}