Skip to main content

bpx/
header.rs

1// Copyright (c) 2021, BlockProject 3D
2//
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without modification,
6// are permitted provided that the following conditions are met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//     * Redistributions in binary form must reproduce the above copyright notice,
11//       this list of conditions and the following disclaimer in the documentation
12//       and/or other materials provided with the distribution.
13//     * Neither the name of BlockProject 3D nor the names of its contributors
14//       may be used to endorse or promote products derived from this software
15//       without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29//! Declarations for basic constants and low-level file headers.
30
31use std::io;
32
33use byteorder::{ByteOrder, LittleEndian};
34
35use super::garraylen::*;
36use crate::{error::Error, Result};
37
38/// The size in bytes of the BPX Main Header.
39pub const SIZE_MAIN_HEADER: usize = 40;
40
41/// The size in bytes of a BPX Section Header.
42pub const SIZE_SECTION_HEADER: usize = 24;
43
44/// XZ section compression enable flag.
45pub const FLAG_COMPRESS_XZ: u8 = 0x2;
46
47/// Section weak checksum enable flag.
48pub const FLAG_CHECK_WEAK: u8 = 0x8;
49
50/// ZLIB section compression enable flag.
51pub const FLAG_COMPRESS_ZLIB: u8 = 0x1;
52
53/// Section CRC32 checksum enable flag.
54pub const FLAG_CHECK_CRC32: u8 = 0x4;
55
56/// The standard variant for a BPX Strings section.
57pub const SECTION_TYPE_STRING: u8 = 0xFF;
58
59/// The standard variant for a BPX Structured Data section.
60pub const SECTION_TYPE_SD: u8 = 0xFE;
61
62/// The BPX version this crate supports.
63pub const BPX_CURRENT_VERSION: u32 = 0x2;
64
65/// The values allowed for the version field in BPX main header.
66pub const KNOWN_VERSIONS: &[u32] = &[0x1, 0x2];
67
68/// The BPX Main Header.
69#[derive(Copy, Clone)]
70pub struct MainHeader
71{
72    /// BPX signature.
73    ///
74    /// Offset: +0
75    pub signature: [u8; 3],
76
77    /// Type byte.
78    ///
79    /// Offset: +3
80    pub btype: u8,
81
82    /// Weak checksum of all headers.
83    ///
84    /// Offset: +4
85    pub chksum: u32,
86
87    /// Total size of BPX in bytes.
88    ///
89    /// Offset: +8
90    pub file_size: u64,
91
92    /// Number of sections.
93    ///
94    /// Offset: +16
95    pub section_num: u32,
96
97    /// Version of BPX.
98    ///
99    /// Offset: +20
100    pub version: u32,
101
102    /// Extended Type Information.
103    ///
104    /// Offset: +24
105    pub type_ext: [u8; 16]
106}
107
108impl MainHeader
109{
110    /// Attempts to read a BPX Main Header from an IO backend.
111    ///
112    /// # Arguments
113    ///
114    /// * `reader`: the IO backend to read from.
115    ///
116    /// returns: Result<(u32, MainHeader), Error>
117    ///
118    /// # Errors
119    ///
120    /// Returns an [Error](crate::error::Error) if the data could not be
121    /// read from the IO backend or if the header is corrupted.
122    ///
123    /// # Examples
124    ///
125    /// ```should_panic
126    /// use bpx::header::{MainHeader, SIZE_MAIN_HEADER};
127    ///
128    /// let mut corrupted: [u8; SIZE_MAIN_HEADER] = [0; SIZE_MAIN_HEADER];
129    /// MainHeader::read(&mut corrupted.as_ref()).unwrap();
130    /// ```
131    pub fn read<TReader: io::Read>(reader: &mut TReader) -> Result<(u32, MainHeader)>
132    {
133        let mut buf: [u8; SIZE_MAIN_HEADER] = [0; SIZE_MAIN_HEADER];
134        let mut checksum: u32 = 0;
135
136        reader.read(&mut buf)?;
137        for i in 0..SIZE_MAIN_HEADER {
138            if i < 4 || i > 7 {
139                checksum += buf[i] as u32;
140            }
141        }
142        let head = MainHeader {
143            signature: extract_slice::<T3>(&buf, 0),
144            btype: buf[3],
145            chksum: LittleEndian::read_u32(&buf[4..8]),
146            file_size: LittleEndian::read_u64(&buf[8..16]),
147            section_num: LittleEndian::read_u32(&buf[16..20]),
148            version: LittleEndian::read_u32(&buf[20..24]),
149            type_ext: extract_slice::<T16>(&buf, 24)
150        };
151        if head.signature[0] != 'B' as u8 || head.signature[1] != 'P' as u8 || head.signature[2] != 'X' as u8 {
152            return Err(Error::Corruption(format!(
153                "incorrect signature, expected {}{}{}, got {}{}{}",
154                'B' as u8, 'P' as u8, 'X' as u8, head.signature[0], head.signature[1], head.signature[2]
155            )));
156        }
157        if !KNOWN_VERSIONS.contains(&head.version) {
158            return Err(Error::Unsupported(format!("unsupported version {}", head.version)));
159        }
160        return Ok((checksum, head));
161    }
162
163    /// Creates a new empty BPX Main Header.
164    pub fn new() -> MainHeader
165    {
166        return MainHeader {
167            signature: ['B' as u8, 'P' as u8, 'X' as u8], //+0
168            btype: 'P' as u8,                             //+3
169            chksum: 0,                                    //+4
170            file_size: SIZE_MAIN_HEADER as u64,           //+8
171            section_num: 0,                               //+16
172            version: BPX_CURRENT_VERSION,                 //+20
173            type_ext: [0; 16]
174        };
175    }
176
177    fn to_bytes(&self) -> [u8; SIZE_MAIN_HEADER]
178    {
179        let mut block: [u8; SIZE_MAIN_HEADER] = [0; SIZE_MAIN_HEADER];
180        block[0] = self.signature[0];
181        block[1] = self.signature[1];
182        block[2] = self.signature[2];
183        block[3] = self.btype;
184        LittleEndian::write_u32(&mut block[4..8], self.chksum);
185        LittleEndian::write_u64(&mut block[8..16], self.file_size);
186        LittleEndian::write_u32(&mut block[16..20], self.section_num);
187        LittleEndian::write_u32(&mut block[20..24], self.version);
188        for i in 24..40 {
189            block[i] = self.type_ext[i - 24];
190        }
191        return block;
192    }
193
194    /// Computes the checksum for this header.
195    pub fn get_checksum(&self) -> u32
196    {
197        let mut checksum: u32 = 0;
198        let buf = self.to_bytes();
199        for i in 0..SIZE_MAIN_HEADER {
200            checksum += buf[i] as u32;
201        }
202        return checksum;
203    }
204
205    /// Attempts to write this header to an IO backend.
206    ///
207    /// # Arguments
208    ///
209    /// * `writer`: the IO backend to write to.
210    ///
211    /// returns: Result<(), Error>
212    ///
213    /// # Errors
214    ///
215    /// Returns an [Error](crate::error::Error) if the data could not be
216    /// written to the IO backend.
217    pub fn write<TWriter: io::Write>(&self, writer: &mut TWriter) -> io::Result<()>
218    {
219        let buf = self.to_bytes();
220        writer.write(&buf)?;
221        writer.flush()?;
222        return Ok(());
223    }
224}
225
226/// The BPX Section Header.
227#[derive(Copy, Clone)]
228pub struct SectionHeader
229{
230    /// Data pointer.
231    ///
232    /// Offset: +0
233    pub pointer: u64,
234
235    /// Size in bytes after compression.
236    ///
237    /// Offset: +8
238    pub csize: u32,
239
240    /// Size in bytes before compression.
241    ///
242    /// Offset: +12
243    pub size: u32,
244
245    /// Data checksum.
246    ///
247    /// Offset: +16
248    pub chksum: u32,
249
250    /// Type byte.
251    ///
252    /// Offset: +20
253    pub btype: u8,
254
255    /// Flags (see FLAG_* constants).
256    ///
257    /// Offset: +21
258    pub flags: u8
259}
260
261impl SectionHeader
262{
263    /// Attempts to read a BPX Section Header from an IO backend.
264    ///
265    /// # Arguments
266    ///
267    /// * `reader`: the IO backend to read from.
268    ///
269    /// returns: Result<(u32, SectionHeader), Error>
270    ///
271    /// # Errors
272    ///
273    /// Returns an [Error](crate::error::Error) if the data could not be
274    /// read from the IO backend.
275    pub fn read<TReader: io::Read>(reader: &mut TReader) -> io::Result<(u32, SectionHeader)>
276    {
277        let mut buf: [u8; SIZE_SECTION_HEADER] = [0; SIZE_SECTION_HEADER];
278        let mut checksum: u32 = 0;
279
280        reader.read(&mut buf)?;
281        for i in 0..SIZE_SECTION_HEADER {
282            checksum += buf[i] as u32;
283        }
284        return Ok((
285            checksum,
286            SectionHeader {
287                pointer: LittleEndian::read_u64(&buf[0..8]),
288                csize: LittleEndian::read_u32(&buf[8..12]),
289                size: LittleEndian::read_u32(&buf[12..16]),
290                chksum: LittleEndian::read_u32(&buf[16..20]),
291                btype: buf[20],
292                flags: buf[21]
293            }
294        ));
295    }
296
297    /// Creates a new empty BPX Section Header.
298    pub fn new() -> SectionHeader
299    {
300        return SectionHeader {
301            pointer: 0, //+0
302            csize: 0,   //+8
303            size: 0,    //+12
304            chksum: 0,  //+16
305            btype: 0,   //+20
306            flags: 0    //+21
307        };
308    }
309
310    /// Checks if this section is huge (greater than 100Mb).
311    pub fn is_huge_section(&self) -> bool
312    {
313        return self.size > 100000000;
314    }
315
316    fn to_bytes(&self) -> [u8; SIZE_SECTION_HEADER]
317    {
318        let mut block: [u8; SIZE_SECTION_HEADER] = [0; SIZE_SECTION_HEADER];
319        LittleEndian::write_u64(&mut block[0..8], self.pointer);
320        LittleEndian::write_u32(&mut block[8..12], self.csize);
321        LittleEndian::write_u32(&mut block[12..16], self.size);
322        LittleEndian::write_u32(&mut block[16..20], self.chksum);
323        block[20] = self.btype;
324        block[21] = self.flags;
325        return block;
326    }
327
328    /// Computes the checksum for this header.
329    pub fn get_checksum(&self) -> u32
330    {
331        let mut checksum: u32 = 0;
332        let buf = self.to_bytes();
333        for i in 0..SIZE_SECTION_HEADER {
334            checksum += buf[i] as u32;
335        }
336        return checksum;
337    }
338
339    /// Attempts to write this header to an IO backend.
340    ///
341    /// # Arguments
342    ///
343    /// * `writer`: the IO backend to write to.
344    ///
345    /// returns: Result<(), Error>
346    ///
347    /// # Errors
348    ///
349    /// Returns an [Error](crate::error::Error) if the data could not be
350    /// written to the IO backend.
351    pub fn write<TWriter: io::Write>(&self, writer: &mut TWriter) -> io::Result<()>
352    {
353        let buf = self.to_bytes();
354        writer.write(&buf)?;
355        writer.flush()?;
356        return Ok(());
357    }
358}