zvault/src/bundledb/reader.rs

238 lines
10 KiB
Rust
Raw Normal View History

2017-03-21 10:28:11 +00:00
use ::prelude::*;
use super::*;
2017-03-21 10:08:01 +00:00
use std::path::{Path, PathBuf};
use std::fs::{self, File};
2017-03-22 08:19:16 +00:00
use std::io::{self, Read, Seek, SeekFrom, BufReader};
2017-03-21 10:08:01 +00:00
use std::cmp::max;
use std::fmt::{self, Debug};
use std::sync::{Arc, Mutex};
2017-03-22 08:19:16 +00:00
quick_error!{
#[derive(Debug)]
pub enum BundleReaderError {
Read(err: io::Error, path: PathBuf) {
cause(err)
context(path: &'a Path, err: io::Error) -> (err, path.to_path_buf())
description("Failed to read data from file")
display("Bundle reader error: failed to read data from file {:?}\n\tcaused by: {}", path, err)
}
WrongHeader(path: PathBuf) {
description("Wrong header")
display("Bundle reader error: wrong header on bundle {:?}", path)
}
UnsupportedVersion(path: PathBuf, version: u8) {
description("Wrong version")
display("Bundle reader error: unsupported version on bundle {:?}: {}", path, version)
}
NoSuchChunk(bundle: BundleId, id: usize) {
description("Bundle has no such chunk")
display("Bundle reader error: bundle {:?} has no chunk with id {}", bundle, id)
}
Decode(err: msgpack::DecodeError, path: PathBuf) {
cause(err)
context(path: &'a Path, err: msgpack::DecodeError) -> (err, path.to_path_buf())
description("Failed to decode bundle header")
display("Bundle reader error: failed to decode bundle header of {:?}\n\tcaused by: {}", path, err)
}
Decompression(err: CompressionError, path: PathBuf) {
cause(err)
context(path: &'a Path, err: CompressionError) -> (err, path.to_path_buf())
description("Decompression failed")
display("Bundle reader error: decompression failed on bundle {:?}\n\tcaused by: {}", path, err)
}
Decryption(err: EncryptionError, path: PathBuf) {
cause(err)
context(path: &'a Path, err: EncryptionError) -> (err, path.to_path_buf())
description("Decryption failed")
display("Bundle reader error: decryption failed on bundle {:?}\n\tcaused by: {}", path, err)
}
Integrity(bundle: BundleId, reason: &'static str) {
description("Bundle has an integrity error")
display("Bundle reader error: bundle {:?} has an integrity error: {}", bundle, reason)
2017-03-21 10:08:01 +00:00
}
}
}
2017-03-22 08:19:16 +00:00
pub struct BundleReader {
2017-03-21 10:08:01 +00:00
pub info: BundleInfo,
pub version: u8,
pub path: PathBuf,
crypto: Arc<Mutex<Crypto>>,
pub content_start: usize,
2017-03-21 12:44:30 +00:00
pub chunks: Option<ChunkList>,
pub chunk_positions: Option<Vec<usize>>
2017-03-21 10:08:01 +00:00
}
2017-03-22 08:19:16 +00:00
impl BundleReader {
2017-03-21 12:44:30 +00:00
pub fn new(path: PathBuf, version: u8, content_start: usize, crypto: Arc<Mutex<Crypto>>, info: BundleInfo) -> Self {
2017-03-22 08:19:16 +00:00
BundleReader {
2017-03-21 10:08:01 +00:00
info: info,
2017-03-21 12:44:30 +00:00
chunks: None,
2017-03-21 10:08:01 +00:00
version: version,
path: path,
crypto: crypto,
content_start: content_start,
2017-03-21 12:44:30 +00:00
chunk_positions: None
2017-03-21 10:08:01 +00:00
}
}
#[inline]
pub fn id(&self) -> BundleId {
self.info.id.clone()
}
2017-04-03 05:57:58 +00:00
fn load_header<P: AsRef<Path>>(path: P, crypto: Arc<Mutex<Crypto>>) -> Result<(BundleInfo, u8, usize), BundleReaderError> {
2017-03-21 14:38:42 +00:00
let path = path.as_ref();
let mut file = BufReader::new(try!(File::open(path).context(path)));
2017-03-21 10:08:01 +00:00
let mut header = [0u8; 8];
2017-03-21 14:38:42 +00:00
try!(file.read_exact(&mut header).context(path));
2017-03-21 10:08:01 +00:00
if header[..HEADER_STRING.len()] != HEADER_STRING {
2017-03-22 08:19:16 +00:00
return Err(BundleReaderError::WrongHeader(path.to_path_buf()))
2017-03-21 10:08:01 +00:00
}
let version = header[HEADER_STRING.len()];
if version != HEADER_VERSION {
2017-03-22 08:19:16 +00:00
return Err(BundleReaderError::UnsupportedVersion(path.to_path_buf(), version))
2017-03-21 10:08:01 +00:00
}
2017-04-03 05:57:58 +00:00
let header: BundleHeader = try!(msgpack::decode_from_stream(&mut file).context(path));
let mut info_data = Vec::with_capacity(header.info_size);
info_data.resize(header.info_size, 0);
try!(file.read_exact(&mut info_data).context(path));
if let Some(ref encryption) = header.encryption {
info_data = try!(crypto.lock().unwrap().decrypt(&encryption, &info_data).context(path));
}
let mut info: BundleInfo = try!(msgpack::decode(&info_data).context(path));
info.encryption = header.encryption;
debug!("Load bundle {}", info.id);
2017-04-03 12:05:16 +00:00
let content_start = file.seek(SeekFrom::Current(0)).unwrap() as usize + info.chunk_list_size;
2017-04-03 05:57:58 +00:00
Ok((info, version, content_start))
2017-03-21 14:38:42 +00:00
}
#[inline]
2017-04-03 05:57:58 +00:00
pub fn load_info<P: AsRef<Path>>(path: P, crypto: Arc<Mutex<Crypto>>) -> Result<BundleInfo, BundleReaderError> {
Self::load_header(path, crypto).map(|b| b.0)
2017-03-21 14:38:42 +00:00
}
#[inline]
2017-03-22 08:19:16 +00:00
pub fn load(path: PathBuf, crypto: Arc<Mutex<Crypto>>) -> Result<Self, BundleReaderError> {
2017-04-03 05:57:58 +00:00
let (header, version, content_start) = try!(Self::load_header(&path, crypto.clone()));
2017-03-22 08:19:16 +00:00
Ok(BundleReader::new(path, version, content_start, crypto, header))
2017-03-21 12:44:30 +00:00
}
2017-03-22 11:27:17 +00:00
fn load_chunklist(&mut self) -> Result<(), BundleReaderError> {
2017-03-21 12:44:30 +00:00
debug!("Load bundle chunklist {} ({:?})", self.info.id, self.info.mode);
let mut file = BufReader::new(try!(File::open(&self.path).context(&self.path as &Path)));
2017-04-03 12:05:16 +00:00
let len = self.info.chunk_list_size;
2017-03-21 12:44:30 +00:00
let start = self.content_start - len;
try!(file.seek(SeekFrom::Start(start as u64)).context(&self.path as &Path));
let mut chunk_data = Vec::with_capacity(len);
2017-04-03 12:05:16 +00:00
chunk_data.resize(self.info.chunk_list_size, 0);
2017-03-21 12:44:30 +00:00
try!(file.read_exact(&mut chunk_data).context(&self.path as &Path));
if let Some(ref encryption) = self.info.encryption {
chunk_data = try!(self.crypto.lock().unwrap().decrypt(&encryption, &chunk_data).context(&self.path as &Path));
2017-03-21 10:08:01 +00:00
}
let chunks = ChunkList::read_from(&chunk_data);
2017-03-21 12:44:30 +00:00
let mut chunk_positions = Vec::with_capacity(chunks.len());
let mut pos = 0;
for &(_, len) in (&chunks).iter() {
chunk_positions.push(pos);
pos += len as usize;
}
self.chunks = Some(chunks);
self.chunk_positions = Some(chunk_positions);
Ok(())
2017-03-21 10:08:01 +00:00
}
2017-03-22 11:27:17 +00:00
#[inline]
pub fn get_chunk_list(&mut self) -> Result<&ChunkList, BundleReaderError> {
if self.chunks.is_none() {
try!(self.load_chunklist());
}
Ok(self.chunks.as_ref().unwrap())
}
2017-03-22 08:19:16 +00:00
fn load_encoded_contents(&self) -> Result<Vec<u8>, BundleReaderError> {
2017-03-21 10:08:01 +00:00
debug!("Load bundle data {} ({:?})", self.info.id, self.info.mode);
let mut file = BufReader::new(try!(File::open(&self.path).context(&self.path as &Path)));
try!(file.seek(SeekFrom::Start(self.content_start as u64)).context(&self.path as &Path));
let mut data = Vec::with_capacity(max(self.info.encoded_size, self.info.raw_size)+1024);
try!(file.read_to_end(&mut data).context(&self.path as &Path));
Ok(data)
}
2017-03-22 08:19:16 +00:00
fn decode_contents(&self, mut data: Vec<u8>) -> Result<Vec<u8>, BundleReaderError> {
2017-03-21 10:08:01 +00:00
if let Some(ref encryption) = self.info.encryption {
data = try!(self.crypto.lock().unwrap().decrypt(&encryption, &data).context(&self.path as &Path));
}
if let Some(ref compression) = self.info.compression {
2017-03-24 11:52:01 +00:00
let mut stream = try!(compression.decompress_stream().context(&self.path as &Path));
let mut buffer = Vec::with_capacity(self.info.raw_size);
try!(stream.process(&data, &mut buffer).context(&self.path as &Path));
try!(stream.finish(&mut buffer).context(&self.path as &Path));
data = buffer;
2017-03-21 10:08:01 +00:00
}
Ok(data)
}
#[inline]
2017-03-22 08:19:16 +00:00
pub fn load_contents(&self) -> Result<Vec<u8>, BundleReaderError> {
2017-03-21 10:08:01 +00:00
self.load_encoded_contents().and_then(|data| self.decode_contents(data))
}
2017-03-22 08:19:16 +00:00
pub fn get_chunk_position(&mut self, id: usize) -> Result<(usize, usize), BundleReaderError> {
2017-03-21 10:08:01 +00:00
if id >= self.info.chunk_count {
2017-03-22 08:19:16 +00:00
return Err(BundleReaderError::NoSuchChunk(self.id(), id))
2017-03-21 10:08:01 +00:00
}
2017-03-21 12:44:30 +00:00
if self.chunks.is_none() || self.chunk_positions.is_none() {
try!(self.load_chunklist());
}
let pos = self.chunk_positions.as_ref().unwrap()[id];
let len = self.chunks.as_ref().unwrap()[id].1 as usize;
Ok((pos, len))
2017-03-21 10:08:01 +00:00
}
2017-03-22 08:19:16 +00:00
pub fn check(&mut self, full: bool) -> Result<(), BundleReaderError> {
2017-03-21 12:44:30 +00:00
if self.chunks.is_none() || self.chunk_positions.is_none() {
try!(self.load_chunklist());
}
if self.info.chunk_count != self.chunks.as_ref().unwrap().len() {
2017-03-22 08:19:16 +00:00
return Err(BundleReaderError::Integrity(self.id(),
2017-03-21 10:08:01 +00:00
"Chunk list size does not match chunk count"))
}
2017-03-21 12:44:30 +00:00
if self.chunks.as_ref().unwrap().iter().map(|c| c.1 as usize).sum::<usize>() != self.info.raw_size {
2017-03-22 08:19:16 +00:00
return Err(BundleReaderError::Integrity(self.id(),
2017-03-21 10:08:01 +00:00
"Individual chunk sizes do not add up to total size"))
}
if !full {
let size = try!(fs::metadata(&self.path).context(&self.path as &Path)).len();
if size as usize != self.info.encoded_size + self.content_start {
2017-03-22 08:19:16 +00:00
return Err(BundleReaderError::Integrity(self.id(),
2017-03-21 10:08:01 +00:00
"File size does not match size in header, truncated file"))
}
return Ok(())
}
let encoded_contents = try!(self.load_encoded_contents());
if self.info.encoded_size != encoded_contents.len() {
2017-03-22 08:19:16 +00:00
return Err(BundleReaderError::Integrity(self.id(),
2017-03-21 10:08:01 +00:00
"Encoded data size does not match size in header, truncated bundle"))
}
let contents = try!(self.decode_contents(encoded_contents));
if self.info.raw_size != contents.len() {
2017-03-22 08:19:16 +00:00
return Err(BundleReaderError::Integrity(self.id(),
2017-03-21 10:08:01 +00:00
"Raw data size does not match size in header, truncated bundle"))
}
//TODO: verify checksum
Ok(())
}
}
2017-03-22 08:19:16 +00:00
impl Debug for BundleReader {
2017-03-21 10:08:01 +00:00
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(fmt, "Bundle(\n\tid: {}\n\tpath: {:?}\n\tchunks: {}\n\tsize: {}, encoded: {}\n\tcompression: {:?}\n)",
self.info.id.to_string(), self.path, self.info.chunk_count, self.info.raw_size,
self.info.encoded_size, self.info.compression)
}
}