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 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 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 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 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 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 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 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 pub fn new(entries: HashMap<u32, HashEntry>) -> Self {
291 Self { entries }
292 }
293
294 pub fn get_entry(&self, name: &String) -> Option<(&u32, &HashEntry)> {
296 self.entries.iter().find(|(_, entry)| entry.name == *name)
297 }
298
299 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 pub fn new(name: String, output_length: u32) -> Self {
354 Self {
355 id: 0,
356 name,
357 output_length,
358 }
359 }
360
361 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 pub fn add_hash_value(&mut self, name: String, value: Vec<u8>) {
390 self.hashes.insert(name, value);
391 }
392
393 pub fn get_hash_value(&self, name: String) -> Option<&Vec<u8>> {
395 self.hashes.get(&name)
396 }
397
398 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}