|
|
|
@ -1,17 +1,21 @@
|
|
|
|
|
use byteorder::{BigEndian, ByteOrder, ReadBytesExt};
|
|
|
|
|
use rand::AsByteSliceMut;
|
|
|
|
|
use byteorder::{BigEndian, ByteOrder};
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
use std::convert::{TryFrom, TryInto};
|
|
|
|
|
use std::fs::File;
|
|
|
|
|
use std::io::{BufReader, Read};
|
|
|
|
|
use std::io::{Error, ErrorKind};
|
|
|
|
|
|
|
|
|
|
pub const BDF_HDR: &[u8; 11] = b"BDF\x01RAINBOW";
|
|
|
|
|
pub const NULL_BYTES: &[u8; 4] = &[0u8; 4];
|
|
|
|
|
pub const META_CHUNK_NAME: &str = "META";
|
|
|
|
|
pub const HTBL_CHUNK_NAME: &str = "HTBL";
|
|
|
|
|
pub const DTBL_CHUNK_NAME: &str = "DTBL";
|
|
|
|
|
|
|
|
|
|
pub struct BinaryDictionaryFile {
|
|
|
|
|
name: String,
|
|
|
|
|
reader: BufReader<File>,
|
|
|
|
|
metadata: Option<MetaChunk>,
|
|
|
|
|
lookup_table: Option<HashLookupTable>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
@ -30,6 +34,11 @@ pub struct MetaChunk {
|
|
|
|
|
compression_method: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct HashLookupTable {
|
|
|
|
|
entries: HashMap<u32, HashEntry>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct HashEntry {
|
|
|
|
|
id: u32,
|
|
|
|
@ -44,22 +53,48 @@ pub struct DataEntry {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl BinaryDictionaryFile {
|
|
|
|
|
fn new(reader: BufReader<File>) -> Self {
|
|
|
|
|
pub fn new(reader: BufReader<File>) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
name: "".to_string(),
|
|
|
|
|
metadata: None,
|
|
|
|
|
lookup_table: None,
|
|
|
|
|
reader,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn read_metadata(&mut self) -> Result<MetaChunk, Error> {
|
|
|
|
|
pub fn read_metadata(&mut self) -> Result<&MetaChunk, Error> {
|
|
|
|
|
if !self.validate_header() {
|
|
|
|
|
return Err(Error::new(ErrorKind::InvalidData, "Invalid BDF Header!"));
|
|
|
|
|
return Err(Error::new(ErrorKind::InvalidData, "invalid BDF Header"));
|
|
|
|
|
}
|
|
|
|
|
let meta_chunk: MetaChunk = self.next_chunk()?.try_into()?;
|
|
|
|
|
self.metadata = Some(meta_chunk);
|
|
|
|
|
|
|
|
|
|
if let Some(chunk) = &self.metadata {
|
|
|
|
|
Ok(&chunk)
|
|
|
|
|
} else {
|
|
|
|
|
Err(Error::new(
|
|
|
|
|
ErrorKind::Other,
|
|
|
|
|
"Failed to read self assigned metadata.",
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
let meta_chunk = self.next_chunk().as_meta_chunk();
|
|
|
|
|
self.metadata = Some(meta_chunk.clone());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn read_lookup_table(&mut self) -> Result<&HashLookupTable, Error> {
|
|
|
|
|
match &self.metadata {
|
|
|
|
|
None => self.read_metadata()?,
|
|
|
|
|
Some(t) => t,
|
|
|
|
|
};
|
|
|
|
|
let lookup_table: HashLookupTable = self.next_chunk()?.try_into()?;
|
|
|
|
|
self.lookup_table = Some(lookup_table);
|
|
|
|
|
|
|
|
|
|
Ok(meta_chunk)
|
|
|
|
|
if let Some(chunk) = &self.lookup_table {
|
|
|
|
|
Ok(&chunk)
|
|
|
|
|
} else {
|
|
|
|
|
Err(Error::new(
|
|
|
|
|
ErrorKind::Other,
|
|
|
|
|
"failed to read self assigned chunk",
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn validate_header(&mut self) -> bool {
|
|
|
|
@ -69,52 +104,109 @@ impl BinaryDictionaryFile {
|
|
|
|
|
header == BDF_HDR.as_ref()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn next_chunk(&mut self) -> GenericChunk {
|
|
|
|
|
/// Returns the next chunk if one is available.
|
|
|
|
|
pub fn next_chunk(&mut self) -> Result<GenericChunk, Error> {
|
|
|
|
|
let mut length_raw = [0u8; 4];
|
|
|
|
|
let _ = self.reader.read(&mut length_raw);
|
|
|
|
|
let _ = self.reader.read_exact(&mut length_raw)?;
|
|
|
|
|
let length = BigEndian::read_u32(&mut length_raw);
|
|
|
|
|
let mut name_raw = [0u8; 4];
|
|
|
|
|
let _ = self.reader.read(&mut name_raw);
|
|
|
|
|
let name =
|
|
|
|
|
String::from_utf8(name_raw.to_vec()).expect("Failed to parse chunk name to string!");
|
|
|
|
|
let _ = self.reader.read_exact(&mut name_raw)?;
|
|
|
|
|
let name = String::from_utf8(name_raw.to_vec()).expect("Failed to parse name string.");
|
|
|
|
|
let mut data = vec![0u8; length as usize];
|
|
|
|
|
let _ = self.reader.read(&mut data);
|
|
|
|
|
let _ = self.reader.read_exact(&mut data)?;
|
|
|
|
|
let mut crc_raw = [0u8; 4];
|
|
|
|
|
let _ = self.reader.read(&mut crc_raw);
|
|
|
|
|
let _ = self.reader.read_exact(&mut crc_raw)?;
|
|
|
|
|
let crc = BigEndian::read_u32(&mut crc_raw);
|
|
|
|
|
|
|
|
|
|
GenericChunk {
|
|
|
|
|
Ok(GenericChunk {
|
|
|
|
|
length,
|
|
|
|
|
name,
|
|
|
|
|
data,
|
|
|
|
|
crc,
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl GenericChunk {
|
|
|
|
|
fn as_meta_chunk(&self) -> MetaChunk {
|
|
|
|
|
let mut chunk_count_raw = self.data[0..4].to_vec();
|
|
|
|
|
let mut entries_per_chunk = self.data[4..8].to_vec();
|
|
|
|
|
let mut total_number_of_entries = self.data[8..12].to_vec();
|
|
|
|
|
let mut compression_method_raw = self.data[12..16].to_vec();
|
|
|
|
|
let chunk_count = BigEndian::read_u32(&mut chunk_count_raw);
|
|
|
|
|
let entries_per_chunk = BigEndian::read_u32(&mut entries_per_chunk);
|
|
|
|
|
let entry_count = BigEndian::read_u32(&mut total_number_of_entries);
|
|
|
|
|
impl TryFrom<GenericChunk> for MetaChunk {
|
|
|
|
|
type Error = Error;
|
|
|
|
|
|
|
|
|
|
fn try_from(chunk: GenericChunk) -> Result<MetaChunk, Error> {
|
|
|
|
|
if &chunk.name != HTBL_CHUNK_NAME {
|
|
|
|
|
return Err(Error::new(
|
|
|
|
|
ErrorKind::InvalidData,
|
|
|
|
|
"chunk name doesn't match",
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
if chunk.data.len() < 16 {
|
|
|
|
|
return Err(Error::new(ErrorKind::InvalidData, "invalid chunk data"));
|
|
|
|
|
}
|
|
|
|
|
let chunk_count_raw = &chunk.data[0..4];
|
|
|
|
|
let entries_per_chunk = &chunk.data[4..8];
|
|
|
|
|
let total_number_of_entries = &chunk.data[8..12];
|
|
|
|
|
let compression_method_raw = chunk.data[12..16].to_vec();
|
|
|
|
|
let chunk_count = BigEndian::read_u32(chunk_count_raw);
|
|
|
|
|
let entries_per_chunk = BigEndian::read_u32(entries_per_chunk);
|
|
|
|
|
let entry_count = BigEndian::read_u32(total_number_of_entries);
|
|
|
|
|
let compression_method = if &compression_method_raw != NULL_BYTES {
|
|
|
|
|
Some(
|
|
|
|
|
String::from_utf8(compression_method_raw.to_vec())
|
|
|
|
|
.expect("Failed to parse compression method from meta string"),
|
|
|
|
|
String::from_utf8(compression_method_raw)
|
|
|
|
|
.expect("Failed to parse compression method name!"),
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MetaChunk {
|
|
|
|
|
Ok(MetaChunk {
|
|
|
|
|
chunk_count,
|
|
|
|
|
entries_per_chunk,
|
|
|
|
|
entry_count,
|
|
|
|
|
compression_method,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl HashLookupTable {
|
|
|
|
|
pub fn get_entry(&self, id: u32) -> Option<&HashEntry> {
|
|
|
|
|
self.entries.get(&id)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TryFrom<GenericChunk> for HashLookupTable {
|
|
|
|
|
type Error = Error;
|
|
|
|
|
|
|
|
|
|
fn try_from(chunk: GenericChunk) -> Result<HashLookupTable, Error> {
|
|
|
|
|
if &chunk.name != HTBL_CHUNK_NAME {
|
|
|
|
|
return Err(Error::new(
|
|
|
|
|
ErrorKind::InvalidData,
|
|
|
|
|
"chunk name doesn't match",
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
let mut hash_entries: HashMap<u32, HashEntry> = HashMap::new();
|
|
|
|
|
let mut position = 0;
|
|
|
|
|
while chunk.data.len() > (position + 12) {
|
|
|
|
|
let id_raw = &chunk.data[position..position + 4];
|
|
|
|
|
position += 4;
|
|
|
|
|
let output_length_raw = &chunk.data[position..position + 4];
|
|
|
|
|
position += 4;
|
|
|
|
|
let name_length_raw = &chunk.data[position..position + 4];
|
|
|
|
|
position += 4;
|
|
|
|
|
let id = BigEndian::read_u32(id_raw);
|
|
|
|
|
let output_length = BigEndian::read_u32(output_length_raw);
|
|
|
|
|
let name_length = BigEndian::read_u32(name_length_raw);
|
|
|
|
|
let name_raw = &chunk.data[position..position + name_length as usize];
|
|
|
|
|
let name =
|
|
|
|
|
String::from_utf8(name_raw.to_vec()).expect("Failed to parse hash function name!");
|
|
|
|
|
hash_entries.insert(
|
|
|
|
|
id,
|
|
|
|
|
HashEntry {
|
|
|
|
|
id,
|
|
|
|
|
output_length,
|
|
|
|
|
name,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
Ok(HashLookupTable {
|
|
|
|
|
entries: hash_entries,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|