bdf/
chunks.rs

1use byteorder::{BigEndian, ByteOrder};
2use crc::crc32;
3use std::collections::HashMap;
4use std::convert::TryFrom;
5use std::io::Read;
6use std::io::{Error, ErrorKind};
7use xz2::read::{XzDecoder, XzEncoder};
8
9pub const LZMA: &str = "lzma";
10
11pub const BDF_HDR: &[u8; 11] = b"BDF\x01RAINBOW";
12pub const NULL_BYTES: &[u8; 4] = &[0u8; 4];
13pub const META_CHUNK_NAME: &str = "META";
14pub const HTBL_CHUNK_NAME: &str = "HTBL";
15pub const DTBL_CHUNK_NAME: &str = "DTBL";
16
17
18#[derive(Debug, Clone)]
19pub struct GenericChunk {
20    pub length: u32,
21    pub(crate) name: String,
22    pub data: Vec<u8>,
23    pub crc: u32,
24}
25
26
27#[derive(Debug, Clone)]
28pub struct MetaChunk {
29    pub chunk_count: u32,
30    pub entries_per_chunk: u32,
31    pub entry_count: u64,
32    pub compression_method: Option<String>,
33}
34
35
36#[derive(Debug, Clone)]
37pub struct HashLookupTable {
38    pub entries: HashMap<u32, HashEntry>,
39}
40
41
42#[derive(Debug, Clone)]
43pub struct HashEntry {
44    pub(crate) id: u32,
45    output_length: u32,
46    name: String,
47}
48
49
50#[derive(Debug, Clone)]
51pub struct DataEntry {
52    pub plain: String,
53    hashes: HashMap<String, Vec<u8>>,
54}
55
56impl GenericChunk {
57    /// Serializes the chunk to a vector of bytes
58    pub fn serialize(&mut self) -> Vec<u8> {
59        let mut serialized: Vec<u8> = Vec::new();
60        let mut length_raw = [0u8; 4];
61        BigEndian::write_u32(&mut length_raw, self.length);
62        serialized.append(&mut length_raw.to_vec());
63        let name_raw = self.name.as_bytes();
64        serialized.append(&mut name_raw.to_vec());
65        serialized.append(&mut self.data);
66        let mut crc_raw = [0u8; 4];
67        BigEndian::write_u32(&mut crc_raw, self.crc);
68        serialized.append(&mut crc_raw.to_vec());
69
70        serialized
71    }
72
73    /// Returns the data entries of the chunk
74    pub fn data_entries(
75        &mut self,
76        lookup_table: &HashLookupTable,
77    ) -> Result<Vec<DataEntry>, Error> {
78        if self.name == HTBL_CHUNK_NAME.to_string() {
79            return Err(Error::new(ErrorKind::Other, "this is not a data chunk"));
80        }
81        let mut entries: Vec<DataEntry> = Vec::new();
82        let mut position = 0;
83
84        while self.data.len() > (position + 8) {
85            let entry_length_raw = &self.data[position..position + 4];
86            position += 4;
87            let entry_length = BigEndian::read_u32(entry_length_raw);
88            let entry_end = position + entry_length as usize;
89            let pw_length_raw = &self.data[position..position + 4];
90            position += 4;
91            let pw_length = BigEndian::read_u32(pw_length_raw);
92            let pw_plain_raw = &self.data[position..position + pw_length as usize];
93            position += pw_length as usize;
94
95            let pw_plain = String::from_utf8(pw_plain_raw.to_vec())
96                .map_err(|err| {
97                    format!(
98                        "failed to parse plain password string ({}-{}): {:?}",
99                        position,
100                        position + pw_length as usize,
101                        err
102                    )
103                })
104                .unwrap();
105            let mut hash_values: HashMap<String, Vec<u8>> = HashMap::new();
106            while position < entry_end {
107                let entry_id_raw = &self.data[position..position + 4];
108                position += 4;
109                let entry_id = BigEndian::read_u32(entry_id_raw);
110
111                if let Some(hash_entry) = lookup_table.entries.get(&entry_id) {
112                    let hash = &self.data[position..position + hash_entry.output_length as usize];
113                    position += hash_entry.output_length as usize;
114                    hash_values.insert(hash_entry.name.clone(), hash.to_vec());
115                }
116            }
117            entries.push(DataEntry {
118                plain: pw_plain,
119                hashes: hash_values,
120            })
121        }
122
123        Ok(entries)
124    }
125
126    /// Constructs the chunk from a Vec of Data entries and a hash lookup table
127    pub fn from_data_entries(
128        entries: &Vec<DataEntry>,
129        lookup_table: &HashLookupTable,
130    ) -> GenericChunk {
131        let mut serialized_data: Vec<u8> = Vec::new();
132
133        entries.iter().for_each(|entry| {
134            serialized_data.append(&mut entry.serialize(&lookup_table));
135        });
136        let crc_sum = crc32::checksum_ieee(serialized_data.as_slice());
137
138        GenericChunk {
139            length: serialized_data.len() as u32,
140            name: DTBL_CHUNK_NAME.to_string(),
141            data: serialized_data,
142            crc: crc_sum,
143        }
144    }
145
146    /// Compresses the data of the chunk using lzma with a level of 6
147    pub fn compress(&mut self, level: u32) -> Result<(), Error> {
148        let data = self.data.as_slice();
149        let mut compressor = XzEncoder::new(data, level);
150        let mut compressed: Vec<u8> = Vec::new();
151        compressor.read_to_end(&mut compressed)?;
152        self.length = compressed.len() as u32;
153        self.data = compressed;
154
155        Ok(())
156    }
157
158    /// Decompresses the data of the chunk with lzma
159    pub fn decompress(&mut self) -> Result<(), Error> {
160        let data = self.data.as_slice();
161        let mut decompressor = XzDecoder::new(data);
162        let mut decompressed: Vec<u8> = Vec::new();
163        decompressor.read_to_end(&mut decompressed)?;
164        let crc = crc32::checksum_ieee(decompressed.as_slice());
165
166        if crc != self.crc {
167            return Err(Error::new(
168                ErrorKind::InvalidData,
169                "the crc doesn't match the decrypted data",
170            ));
171        }
172        self.length = decompressed.len() as u32;
173        self.data = decompressed;
174
175        Ok(())
176    }
177}
178
179impl From<&MetaChunk> for GenericChunk {
180    fn from(chunk: &MetaChunk) -> GenericChunk {
181        let serialized_data = chunk.serialize();
182        let crc_sum = crc32::checksum_ieee(serialized_data.as_slice());
183
184        GenericChunk {
185            length: serialized_data.len() as u32,
186            name: META_CHUNK_NAME.to_string(),
187            data: serialized_data,
188            crc: crc_sum,
189        }
190    }
191}
192
193impl From<&HashLookupTable> for GenericChunk {
194    fn from(chunk: &HashLookupTable) -> GenericChunk {
195        let serialized_data = chunk.serialize();
196        let crc_sum = crc32::checksum_ieee(serialized_data.as_slice());
197
198        GenericChunk {
199            length: serialized_data.len() as u32,
200            name: HTBL_CHUNK_NAME.to_string(),
201            data: serialized_data,
202            crc: crc_sum,
203        }
204    }
205}
206
207impl MetaChunk {
208    /// Creates a new meta chunk
209    pub fn new(entry_count: u64, entries_per_chunk: u32, compress: bool) -> Self {
210        let compression_method = if compress {
211            Some(LZMA.to_string())
212        } else {
213            None
214        };
215        let chunk_count = (entry_count as f64 / entries_per_chunk as f64).ceil() as u32;
216
217        Self {
218            chunk_count,
219            entry_count,
220            entries_per_chunk,
221            compression_method,
222        }
223    }
224
225    /// Serializes the chunk into bytes
226    pub fn serialize(&self) -> Vec<u8> {
227        let mut serialized_data: Vec<u8> = Vec::new();
228        let mut chunk_count_raw = [0u8; 4];
229        BigEndian::write_u32(&mut chunk_count_raw, self.chunk_count);
230        serialized_data.append(&mut chunk_count_raw.to_vec());
231        let mut entries_pc_raw = [0u8; 4];
232        BigEndian::write_u32(&mut entries_pc_raw, self.entries_per_chunk);
233        serialized_data.append(&mut entries_pc_raw.to_vec());
234        let mut total_entries_raw = [0u8; 8];
235        BigEndian::write_u64(&mut total_entries_raw, self.entry_count);
236        serialized_data.append(&mut total_entries_raw.to_vec());
237        let mut compression_method = self.compression_method.clone();
238
239        if let Some(method) = &mut compression_method {
240            serialized_data.append(&mut method.clone().into_bytes());
241        } else {
242            serialized_data.append(&mut vec![0, 0, 0, 0]);
243        }
244
245        serialized_data
246    }
247}
248
249impl TryFrom<GenericChunk> for MetaChunk {
250    type Error = Error;
251
252    fn try_from(chunk: GenericChunk) -> Result<MetaChunk, Error> {
253        if &chunk.name != META_CHUNK_NAME {
254            return Err(Error::new(
255                ErrorKind::InvalidData,
256                "chunk name doesn't match",
257            ));
258        }
259        if chunk.data.len() < 20 {
260            return Err(Error::new(ErrorKind::InvalidData, "invalid chunk data"));
261        }
262        let chunk_count_raw = &chunk.data[0..4];
263        let entries_per_chunk = &chunk.data[4..8];
264        let total_number_of_entries = &chunk.data[8..16];
265        let compression_method_raw = chunk.data[16..20].to_vec();
266        let chunk_count = BigEndian::read_u32(chunk_count_raw);
267        let entries_per_chunk = BigEndian::read_u32(entries_per_chunk);
268        let entry_count = BigEndian::read_u64(total_number_of_entries);
269        let compression_method = if &compression_method_raw != NULL_BYTES {
270            Some(
271                String::from_utf8(compression_method_raw)
272                    .expect("Failed to parse compression method name!"),
273            )
274        } else {
275            None
276        };
277
278        Ok(MetaChunk {
279            chunk_count,
280            entries_per_chunk,
281            entry_count,
282            compression_method,
283        })
284    }
285}
286
287impl HashLookupTable {
288
289    /// Creates a new hash lookup table
290    pub fn new(entries: HashMap<u32, HashEntry>) -> Self {
291        Self { entries }
292    }
293
294    /// Returns an entry by the name of the hash function
295    pub fn get_entry(&self, name: &String) -> Option<(&u32, &HashEntry)> {
296        self.entries.iter().find(|(_, entry)| entry.name == *name)
297    }
298
299    /// Serializes the lookup table into a vector of bytes
300    pub fn serialize(&self) -> Vec<u8> {
301        let mut serialized_full: Vec<u8> = Vec::new();
302        for (_, entry) in &self.entries {
303            serialized_full.append(entry.serialize().as_mut())
304        }
305
306        serialized_full
307    }
308}
309
310impl TryFrom<GenericChunk> for HashLookupTable {
311    type Error = Error;
312
313    fn try_from(chunk: GenericChunk) -> Result<HashLookupTable, Error> {
314        if &chunk.name != HTBL_CHUNK_NAME {
315            return Err(Error::new(
316                ErrorKind::InvalidData,
317                "chunk name doesn't match",
318            ));
319        }
320        let mut hash_entries: HashMap<u32, HashEntry> = HashMap::new();
321        let mut position = 0;
322        while chunk.data.len() > (position + 12) {
323            let id_raw = &chunk.data[position..position + 4];
324            position += 4;
325            let output_length_raw = &chunk.data[position..position + 4];
326            position += 4;
327            let name_length_raw = &chunk.data[position..position + 4];
328            position += 4;
329            let id = BigEndian::read_u32(id_raw);
330            let output_length = BigEndian::read_u32(output_length_raw);
331            let name_length = BigEndian::read_u32(name_length_raw);
332            let name_raw = &chunk.data[position..position + name_length as usize];
333            let name =
334                String::from_utf8(name_raw.to_vec()).expect("Failed to parse hash function name!");
335            hash_entries.insert(
336                id,
337                HashEntry {
338                    id,
339                    output_length,
340                    name,
341                },
342            );
343        }
344        Ok(HashLookupTable {
345            entries: hash_entries,
346        })
347    }
348}
349
350impl HashEntry {
351
352    /// Creates a new hash entry
353    pub fn new(name: String, output_length: u32) -> Self {
354        Self {
355            id: 0,
356            name,
357            output_length,
358        }
359    }
360
361    /// Serializes the entry to a vector of bytes
362    pub fn serialize(&self) -> Vec<u8> {
363        let mut serialized: Vec<u8> = Vec::new();
364        let mut id_raw = [0u8; 4];
365        BigEndian::write_u32(&mut id_raw, self.id);
366        serialized.append(&mut id_raw.to_vec());
367        let mut output_length_raw = [0u8; 4];
368        BigEndian::write_u32(&mut output_length_raw, self.output_length);
369        serialized.append(&mut output_length_raw.to_vec());
370        let mut name_raw = self.name.clone().into_bytes();
371        let mut name_length_raw = [0u8; 4];
372        BigEndian::write_u32(&mut name_length_raw, name_raw.len() as u32);
373        serialized.append(&mut name_length_raw.to_vec());
374        serialized.append(&mut name_raw);
375
376        serialized
377    }
378}
379
380impl DataEntry {
381    pub fn new(plain: String) -> Self {
382        Self {
383            hashes: HashMap::new(),
384            plain,
385        }
386    }
387
388    /// Adds a hash to the hash values
389    pub fn add_hash_value(&mut self, name: String, value: Vec<u8>) {
390        self.hashes.insert(name, value);
391    }
392
393    /// Returns the hash value for a given name of a hash function
394    pub fn get_hash_value(&self, name: String) -> Option<&Vec<u8>> {
395        self.hashes.get(&name)
396    }
397
398    /// Serializes the entry to a vector of bytes
399    pub fn serialize(&self, lookup_table: &HashLookupTable) -> Vec<u8> {
400        let mut pw_plain_raw = self.plain.clone().into_bytes();
401        let mut pw_length_raw = [0u8; 4];
402        BigEndian::write_u32(&mut pw_length_raw, pw_plain_raw.len() as u32);
403        let mut hash_data: Vec<u8> = Vec::new();
404        for (name, value) in &self.hashes {
405            if let Some((id, _)) = lookup_table.get_entry(&name) {
406                let mut id_raw = [0u8; 4];
407                BigEndian::write_u32(&mut id_raw, *id);
408                hash_data.append(&mut id_raw.to_vec());
409                hash_data.append(&mut value.clone())
410            }
411        }
412
413        let mut length_total_raw = [0u8; 4];
414        BigEndian::write_u32(
415            &mut length_total_raw,
416            4 + pw_plain_raw.len() as u32 + hash_data.len() as u32,
417        );
418        let mut serialized_data: Vec<u8> = Vec::new();
419        serialized_data.append(&mut length_total_raw.to_vec());
420        serialized_data.append(&mut pw_length_raw.to_vec());
421        serialized_data.append(&mut pw_plain_raw);
422        serialized_data.append(&mut hash_data);
423
424        serialized_data
425    }
426}