[go: up one dir, main page]

gltf/
binary.rs

1use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
2use std::borrow::Cow;
3use std::{fmt, io, mem};
4
5/// Represents a Glb loader error.
6#[derive(Debug)]
7pub enum Error {
8    /// Io error occured.
9    Io(::std::io::Error),
10    /// Unsupported version.
11    Version(u32),
12    /// Magic says that file is not glTF.
13    Magic([u8; 4]),
14    /// Length specified in GLB header exceeeds that of slice.
15    Length {
16        /// length specified in GLB header.
17        length: u32,
18        /// Actual length of data read.
19        length_read: usize,
20    },
21    /// Stream ended before we could read the chunk.
22    ChunkLength {
23        /// chunkType error happened at.
24        ty: ChunkType,
25        /// chunkLength.
26        length: u32,
27        /// Actual length of data read.
28        length_read: usize,
29    },
30    /// Chunk of this chunkType was not expected.
31    ChunkType(ChunkType),
32    /// Unknown chunk type.
33    UnknownChunkType([u8; 4]),
34}
35
36/// Binary glTF contents.
37#[derive(Clone, Debug)]
38pub struct Glb<'a> {
39    /// The header section of the `.glb` file.
40    pub header: Header,
41    /// The JSON section of the `.glb` file.
42    pub json: Cow<'a, [u8]>,
43    /// The optional BIN section of the `.glb` file.
44    pub bin: Option<Cow<'a, [u8]>>,
45}
46
47/// The header section of a .glb file.
48#[derive(Copy, Clone, Debug)]
49#[repr(C)]
50pub struct Header {
51    /// Must be `b"glTF"`.
52    pub magic: [u8; 4],
53    /// Must be `2`.
54    pub version: u32,
55    /// Must match the length of the parent .glb file.
56    pub length: u32,
57}
58
59/// GLB chunk type.
60#[derive(Copy, Clone, Debug)]
61pub enum ChunkType {
62    /// `JSON` chunk.
63    Json,
64    /// `BIN` chunk.
65    Bin,
66}
67
68/// Chunk header with no data read yet.
69#[derive(Copy, Clone, Debug)]
70#[repr(C)]
71struct ChunkHeader {
72    /// The length of the chunk data in byte excluding the header.
73    length: u32,
74    /// Chunk type.
75    ty: ChunkType,
76}
77
78impl Header {
79    fn from_reader<R: io::Read>(mut reader: R) -> Result<Self, Error> {
80        use self::Error::Io;
81        let mut magic = [0; 4];
82        reader.read_exact(&mut magic).map_err(Io)?;
83        // We only validate magic as we don't care for version and length of
84        // contents, the caller does.  Let them decide what to do next with
85        // regard to version and length.
86        if &magic == b"glTF" {
87            Ok(Self {
88                magic,
89                version: reader.read_u32::<LittleEndian>().map_err(Io)?,
90                length: reader.read_u32::<LittleEndian>().map_err(Io)?,
91            })
92        } else {
93            Err(Error::Magic(magic))
94        }
95    }
96
97    fn size_of() -> usize {
98        12
99    }
100}
101
102impl ChunkHeader {
103    fn from_reader<R: io::Read>(mut reader: R) -> Result<Self, Error> {
104        use self::Error::Io;
105        let length = reader.read_u32::<LittleEndian>().map_err(Io)?;
106        let mut ty = [0; 4];
107        reader.read_exact(&mut ty).map_err(Io)?;
108        let ty = match &ty {
109            b"JSON" => Ok(ChunkType::Json),
110            b"BIN\0" => Ok(ChunkType::Bin),
111            _ => Err(Error::UnknownChunkType(ty)),
112        }?;
113        Ok(Self { length, ty })
114    }
115}
116
117fn align_to_multiple_of_four(n: &mut usize) {
118    *n = (*n + 3) & !3;
119}
120
121fn split_binary_gltf(mut data: &[u8]) -> Result<(&[u8], Option<&[u8]>), Error> {
122    let (json, mut data) = ChunkHeader::from_reader(&mut data)
123        .and_then(|json_h| {
124            if let ChunkType::Json = json_h.ty {
125                Ok(json_h)
126            } else {
127                Err(Error::ChunkType(json_h.ty))
128            }
129        })
130        .and_then(|json_h| {
131            if json_h.length as usize <= data.len() {
132                Ok(json_h)
133            } else {
134                Err(Error::ChunkLength {
135                    ty: json_h.ty,
136                    length: json_h.length,
137                    length_read: data.len(),
138                })
139            }
140        })
141        // We have verified that json_h.length is no greater than that of
142        // data.len().
143        .map(|json_h| data.split_at(json_h.length as usize))?;
144
145    let bin = if !data.is_empty() {
146        ChunkHeader::from_reader(&mut data)
147            .and_then(|bin_h| {
148                if let ChunkType::Bin = bin_h.ty {
149                    Ok(bin_h)
150                } else {
151                    Err(Error::ChunkType(bin_h.ty))
152                }
153            })
154            .and_then(|bin_h| {
155                if bin_h.length as usize <= data.len() {
156                    Ok(bin_h)
157                } else {
158                    Err(Error::ChunkLength {
159                        ty: bin_h.ty,
160                        length: bin_h.length,
161                        length_read: data.len(),
162                    })
163                }
164            })
165            // We have verified that bin_h.length is no greater than that
166            // of data.len().
167            .map(|bin_h| data.split_at(bin_h.length as usize))
168            .map(|(x, _)| Some(x))?
169    } else {
170        None
171    };
172    Ok((json, bin))
173}
174
175impl<'a> Glb<'a> {
176    /// Writes binary glTF to a writer.
177    pub fn to_writer<W>(&self, mut writer: W) -> Result<(), crate::Error>
178    where
179        W: io::Write,
180    {
181        // Write GLB header
182        {
183            let magic = b"glTF";
184            let version = 2;
185            let mut length =
186                mem::size_of::<Header>() + mem::size_of::<ChunkHeader>() + self.json.len();
187            align_to_multiple_of_four(&mut length);
188            if let Some(bin) = self.bin.as_ref() {
189                length += mem::size_of::<ChunkHeader>() + bin.len();
190                align_to_multiple_of_four(&mut length);
191            }
192
193            writer.write_all(&magic[..])?;
194            writer.write_u32::<LittleEndian>(version)?;
195            writer.write_u32::<LittleEndian>(length as u32)?;
196        }
197
198        // Write JSON chunk header
199        {
200            let magic = b"JSON";
201            let mut length = self.json.len();
202            align_to_multiple_of_four(&mut length);
203            let padding = length - self.json.len();
204
205            writer.write_u32::<LittleEndian>(length as u32)?;
206            writer.write_all(&magic[..])?;
207            writer.write_all(&self.json)?;
208            for _ in 0..padding {
209                writer.write_u8(0x20)?;
210            }
211        }
212
213        if let Some(bin) = self.bin.as_ref() {
214            let magic = b"BIN\0";
215            let mut length = bin.len();
216            align_to_multiple_of_four(&mut length);
217            let padding = length - bin.len();
218
219            writer.write_u32::<LittleEndian>(length as u32)?;
220            writer.write_all(&magic[..])?;
221            writer.write_all(bin)?;
222            for _ in 0..padding {
223                writer.write_u8(0)?;
224            }
225        }
226
227        Ok(())
228    }
229
230    /// Writes binary glTF to a byte vector.
231    pub fn to_vec(&self) -> Result<Vec<u8>, crate::Error> {
232        let mut length = mem::size_of::<Header>() + mem::size_of::<ChunkHeader>() + self.json.len();
233        align_to_multiple_of_four(&mut length);
234        if let Some(bin) = self.bin.as_ref() {
235            length += mem::size_of::<ChunkHeader>() + bin.len();
236            align_to_multiple_of_four(&mut length);
237        }
238
239        let mut vec = Vec::with_capacity(length);
240        self.to_writer(&mut vec as &mut dyn io::Write)?;
241        Ok(vec)
242    }
243
244    /// Splits loaded GLB into its three chunks.
245    ///
246    /// * Mandatory GLB header.
247    /// * Mandatory JSON chunk.
248    /// * Optional BIN chunk.
249    pub fn from_slice(mut data: &'a [u8]) -> Result<Self, crate::Error> {
250        let header = Header::from_reader(&mut data)
251            .and_then(|header| {
252                let contents_length = header.length as usize - Header::size_of();
253                if contents_length <= data.len() {
254                    Ok(header)
255                } else {
256                    Err(Error::Length {
257                        length: contents_length as u32,
258                        length_read: data.len(),
259                    })
260                }
261            })
262            .map_err(crate::Error::Binary)?;
263        match header.version {
264            2 => split_binary_gltf(data)
265                .map(|(json, bin)| Glb {
266                    header,
267                    json: json.into(),
268                    bin: bin.map(Into::into),
269                })
270                .map_err(crate::Error::Binary),
271            x => Err(crate::Error::Binary(Error::Version(x))),
272        }
273    }
274
275    /// Reads binary glTF from a generic stream of data.
276    ///
277    /// # Note
278    ///
279    /// Reading terminates early if the stream does not contain valid binary
280    /// glTF.
281    pub fn from_reader<R: io::Read>(mut reader: R) -> Result<Self, crate::Error> {
282        let header = Header::from_reader(&mut reader).map_err(crate::Error::Binary)?;
283        match header.version {
284            2 => {
285                let glb_len = header.length - Header::size_of() as u32;
286                let mut buf = vec![0; glb_len as usize];
287                if let Err(e) = reader.read_exact(&mut buf).map_err(Error::Io) {
288                    Err(crate::Error::Binary(e))
289                } else {
290                    split_binary_gltf(&buf)
291                        .map(|(json, bin)| Glb {
292                            header,
293                            json: json.to_vec().into(),
294                            bin: bin.map(<[u8]>::to_vec).map(Into::into),
295                        })
296                        .map_err(crate::Error::Binary)
297                }
298            }
299            x => Err(crate::Error::Binary(Error::Version(x))),
300        }
301    }
302}
303
304impl fmt::Display for Error {
305    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
306        write!(
307            f,
308            "{}",
309            match *self {
310                Error::Io(ref e) => return e.fmt(f),
311                Error::Version(_) => "unsupported version",
312                Error::Magic(_) => "not glTF magic",
313                Error::Length { .. } => "could not completely read the object",
314                Error::ChunkLength { ty, .. } => match ty {
315                    ChunkType::Json => "JSON chunk length exceeds that of slice",
316                    ChunkType::Bin => "BIN\\0 chunk length exceeds that of slice",
317                },
318                Error::ChunkType(ty) => match ty {
319                    ChunkType::Json => "was not expecting JSON chunk",
320                    ChunkType::Bin => "was not expecting BIN\\0 chunk",
321                },
322                Error::UnknownChunkType(_) => "unknown chunk type",
323            }
324        )
325    }
326}
327
328impl ::std::error::Error for Error {}