hff_core/
header.rs

1//! The file header.
2use crate::{Ecc, Endian, Error, IdType, Result, Version, BE, LE, NATIVE_ENDIAN, NE};
3use byteorder::{ByteOrder, ReadBytesExt, WriteBytesExt};
4use std::io::Write;
5
6/// The current version of the format.
7pub const FORMAT_VERSION: Version = Version::new(0, 3);
8
9/// The file header.
10#[repr(C)]
11#[derive(Debug, Clone, Eq, PartialEq)]
12pub struct Header {
13    /// Magic identifier.  Ecc::HFF_MAGIC
14    magic: Ecc,
15    /// Version of the file format.
16    version: Version,
17    /// Identification type for tables and chunks.
18    /// NOTE: This is in no way enforced, it is simply a hint for viewers to
19    /// use when displaying the ID's.  Some formats, archives for instance,
20    /// will use a mix of identifiers and this will be set to the most common
21    /// id type.
22    id_type: u32,
23    /// The overall content type of this file.
24    /// Unlike table and chunk ID's, this is always an 8 byte character code.
25    content_type: Ecc,
26    /// Total count of tables in the header.
27    table_count: u32,
28    /// Total count of chunks in the header.
29    chunk_count: u32,
30}
31
32impl Header {
33    /// Size of the header.
34    pub const SIZE: usize = std::mem::size_of::<Self>();
35
36    /// Create a new instance.
37    pub fn new(id_type: IdType, content_type: Ecc, table_count: u32, chunk_count: u32) -> Self {
38        Self {
39            magic: Ecc::HFF_MAGIC,
40            version: FORMAT_VERSION,
41            id_type: *id_type,
42            content_type,
43            table_count,
44            chunk_count,
45        }
46    }
47
48    /// Create a new instance with the given data.
49    pub fn with(
50        magic: Ecc,
51        version: Version,
52        id_type: u32,
53        content_type: Ecc,
54        table_count: u32,
55        chunk_count: u32,
56    ) -> Self {
57        Self {
58            magic,
59            version,
60            id_type,
61            content_type,
62            table_count,
63            chunk_count,
64        }
65    }
66
67    /// Check that this is a valid file header.
68    pub fn is_valid(&self) -> bool {
69        match self.magic.endian(Ecc::HFF_MAGIC) {
70            Some(endian) => {
71                if endian == NATIVE_ENDIAN {
72                    self.version == FORMAT_VERSION
73                } else {
74                    self.version.swap_bytes() == FORMAT_VERSION
75                }
76            }
77            None => false,
78        }
79    }
80
81    /// Get the magic value.
82    pub fn magic(&self) -> Ecc {
83        self.magic
84    }
85
86    /// Get the container version.
87    pub fn version(&self) -> Version {
88        self.version
89    }
90
91    /// Get the content type of the container.
92    pub fn content_type(&self) -> Ecc {
93        self.content_type
94    }
95
96    /// Identifier type.
97    pub fn id_type(&self) -> IdType {
98        self.id_type.into()
99    }
100
101    /// What's the endian?
102    pub fn is_native_endian(&self) -> bool {
103        self.magic == Ecc::HFF_MAGIC
104    }
105
106    /// Get the table count.
107    pub fn table_count(&self) -> u32 {
108        self.table_count
109    }
110
111    /// Get the chunk count.
112    pub fn chunk_count(&self) -> u32 {
113        self.chunk_count
114    }
115
116    /// Convert the header to a byte vector.
117    pub fn to_bytes<E: ByteOrder>(self) -> Result<Vec<u8>> {
118        let mut buffer = vec![];
119        let writer: &mut dyn Write = &mut buffer;
120        self.magic.write::<E>(writer)?;
121        self.version.write::<E>(writer)?;
122        writer.write_u32::<E>(self.id_type)?;
123        self.content_type.write::<E>(writer)?;
124        writer.write_u32::<E>(self.table_count)?;
125        writer.write_u32::<E>(self.chunk_count)?;
126        Ok(buffer)
127    }
128
129    /// A test helper.  Swapping the bytes like this only makes sense for
130    /// testing because the read adjusts to endianess after reading only
131    /// the magic and not the rest.
132    #[cfg(test)]
133    pub fn swap_bytes(&self) -> Self {
134        Self {
135            magic: self.magic.swap_bytes(),
136            version: self.version.swap_bytes(),
137            id_type: self.id_type.swap_bytes(),
138            content_type: self.content_type.swap_bytes(),
139            table_count: self.table_count.swap_bytes(),
140            chunk_count: self.chunk_count.swap_bytes(),
141        }
142    }
143}
144
145impl TryFrom<&[u8]> for Header {
146    type Error = crate::Error;
147
148    fn try_from(mut value: &[u8]) -> std::prelude::v1::Result<Self, Self::Error> {
149        let reader: &mut dyn std::io::Read = &mut value;
150
151        // Read the magic in native endian.
152        let magic = Ecc::read::<NE>(reader)?;
153
154        // Check the endianness and read the remaining data appropriately.
155        // NOTE: The magic is stored as whatever form was found so we can
156        // detect the original form at a later time.
157        match Ecc::HFF_MAGIC.endian(magic.clone()) {
158            Some(endian) => match endian {
159                Endian::Little => Ok(Header::with(
160                    magic,
161                    Version::read::<LE>(reader)?,
162                    reader.read_u32::<LE>()?,
163                    Ecc::read::<LE>(reader)?,
164                    reader.read_u32::<LE>()?,
165                    reader.read_u32::<LE>()?,
166                )),
167                Endian::Big => Ok(Header::with(
168                    magic,
169                    Version::read::<BE>(reader)?,
170                    reader.read_u32::<BE>()?,
171                    Ecc::read::<BE>(reader)?,
172                    reader.read_u32::<BE>()?,
173                    reader.read_u32::<BE>()?,
174                )),
175            },
176            None => Err(Error::Invalid("Not an HFF file.".into())),
177        }
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[test]
186    fn test_struct_layout() {
187        assert_eq!(std::mem::size_of::<Header>(), 32);
188    }
189
190    #[test]
191    fn validation() {
192        assert!(Header::new(IdType::Ecc2, Ecc::new("test"), 0, 0).is_valid());
193        assert!(Header::new(IdType::Ecc2, Ecc::new("test"), 0, 0).is_native_endian());
194        assert!(Header::new(IdType::Ecc2, Ecc::new("test"), 0, 0)
195            .swap_bytes()
196            .is_valid());
197        assert!(!Header::new(IdType::Ecc2, Ecc::new("test"), 0, 0)
198            .swap_bytes()
199            .is_native_endian());
200    }
201
202    #[test]
203    fn serialization() {
204        {
205            // Create a header, convert to bytes and then recreate from the bytes.
206            let header = Header::new(IdType::Ecc2, "Test".into(), 1, 2);
207            let buffer = header.clone().to_bytes::<LE>().unwrap();
208            let dup: Header = buffer.as_slice().try_into().unwrap();
209
210            assert_eq!(dup.magic, Ecc::HFF_MAGIC);
211            assert_eq!(dup.version, FORMAT_VERSION);
212            assert_eq!(dup.content_type, Ecc::new("Test"));
213            assert_eq!(dup.table_count, 1);
214            assert_eq!(dup.chunk_count, 2);
215        }
216
217        {
218            // Create a header, convert to bytes and then recreate from the bytes.
219            let header = Header::new(IdType::Ecc2, "Test".into(), 1, 2);
220            let buffer = header.clone().to_bytes::<BE>().unwrap();
221            let dup: Header = buffer.as_slice().try_into().unwrap();
222
223            assert_eq!(dup.magic, Ecc::HFF_MAGIC.swap_bytes());
224            assert_eq!(dup.version, FORMAT_VERSION);
225            assert_eq!(dup.content_type, Ecc::new("Test"));
226            assert_eq!(dup.table_count, 1);
227            assert_eq!(dup.chunk_count, 2);
228        }
229    }
230}