use crate::data_header::DataHeader;
use crate::data_header::{BlockFlags, BlockSerializer};
use crate::crypto::BlockHasher;
use std::convert::TryFrom;
use std::fmt;
use std::fs::{ File, OpenOptions };
use std::io::{Error, ErrorKind};
use std::io::{Read, Seek, SeekFrom, Write};
use std::marker::PhantomData;
static STORE_VERSIONTAG: &str = "FSTOREV.01BINARYR01";
static STORE_VERSIONNUM: u32 = 1;
static ERROR_FSTORE_VERSION: &str = "Unexpected version info.";
static ERROR_FSTORE_INVALID: &str = "Invalid file descriptor.";
static ERROR_FSTORE_INVSIZE: &str = "Unexpected data size encountered.";
static ERROR_OUTOFBOUNDS: &str = "Value out of bounds.";
#[derive(Debug)]
pub struct StoreError {
error: String,
}
impl StoreError {
fn new(error: String) -> StoreError {
StoreError { error }
}
}
impl fmt::Display for StoreError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.error)
}
}
impl std::error::Error for StoreError {}
pub struct Store<T: BlockHasher> {
file: File,
data_start_address: u64,
block_addresses: Vec<u64>,
phantom: PhantomData<T>,
}
pub trait StoreIO<T: BlockHasher> {
fn delete_block(&mut self, index: usize) -> Result<(), Box<dyn std::error::Error>>;
fn len(&self) -> usize;
fn block_address(&self, index: usize) -> Option<&u64>;
fn read_data_header(
&mut self,
data_header: &mut DataHeader<T>,
) -> Result<(), Box<dyn std::error::Error>>;
fn read(&mut self, data: &mut Vec<u8>) -> Result<usize, Error>;
fn read_at_index(&mut self, index: usize, data: &mut Vec<u8>) -> Result<usize,Box<dyn std::error::Error>>;
fn seek(&mut self, index: usize) -> Result<u64, Box<dyn std::error::Error>>;
}
impl<T: BlockHasher> Store<T> {
pub fn new(filename: String) -> Result<Store<T>, Box<dyn std::error::Error>> {
let v = File::open(filename)?;
let mut st = Store::<T> {
file: v,
data_start_address: 0,
block_addresses: Vec::new(),
phantom: PhantomData,
};
let fd = st.read_file_descriptor()?;
if !Store::<T>::validate_file_descriptor(fd) {
return Err(Box::new(Error::new(
ErrorKind::InvalidData,
ERROR_FSTORE_INVALID,
)));
}
st.index_blocks(0)?;
Ok(st)
}
pub fn create(filename: String) -> Result<Store<T>, Error> {
let mut f = OpenOptions::new().write(true).read(true).create(true).open(filename)?;
Store::<T>::write_file_descriptor(&mut f)?;
Ok(Store::<T> {
file: f,
data_start_address: 0,
block_addresses: Vec::new(),
phantom: PhantomData,
})
}
fn write_file_descriptor(file: &mut File) -> Result<(), Error> {
file.write(&STORE_VERSIONNUM.to_le_bytes())?;
let sz = u64::try_from(STORE_VERSIONTAG.as_bytes().len()).unwrap();
file.write(&sz.to_le_bytes())?;
file.write(&STORE_VERSIONTAG.as_bytes())?;
Ok(())
}
fn read_file_descriptor(&mut self) -> Result<(u32, String), Error> {
self.file.seek(SeekFrom::Start(0))?;
let mut buff = [0u8; 4];
let mut sz_buff = [0u8; 8];
self.file.read(&mut buff)?;
self.file.read(&mut sz_buff)?;
let mut str_buff = vec![0u8; usize::try_from(u64::from_le_bytes(sz_buff)).unwrap()];
self.file.read(&mut str_buff)?;
self.data_start_address = self.file.seek(SeekFrom::Current(0))?;
if let Ok(s) = String::from_utf8(str_buff) {
Ok((u32::from_le_bytes(buff), s))
} else {
return Err(Error::new(ErrorKind::InvalidData, ERROR_FSTORE_VERSION));
}
}
pub fn validate_file_descriptor(value: (u32, String)) -> bool {
if value == (STORE_VERSIONNUM, STORE_VERSIONTAG.to_string()) {
return true;
}
false
}
fn index_blocks(&mut self, startpos: u64) -> Result<(), Box<dyn std::error::Error>> {
self.block_addresses.clear();
let mut curpos = if startpos == 0 {
self.data_start_address
} else {
startpos
};
let buffsize = DataHeader::<T>::read_ahead_size();
let md = self.file.metadata()?;
self.block_addresses.push(curpos);
while curpos < md.len() {
let mut buffer = vec![0u8; buffsize];
self.file.read(&mut buffer)?;
let tbs = DataHeader::<T>::read_ahead(&buffer)?;
curpos = self.file.seek(SeekFrom::Current(tbs))?;
self.block_addresses.push(curpos);
}
self.file.seek(SeekFrom::Start(self.data_start_address))?;
Ok(())
}
}
impl<T: BlockHasher> Write for Store<T> {
fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
if let Ok(mut bd) = DataHeader::<T>::new() {
if let Ok(sd) = bd.serialize(buf) {
self.file.write(sd)?;
} else {
return Err(Error::new(ErrorKind::InvalidInput, ERROR_FSTORE_INVSIZE));
}
let retval = self.file.write(&buf);
self.block_addresses.push(self.file.seek(SeekFrom::Current(0))?);
retval
} else {
return Err(Error::new(ErrorKind::InvalidInput, ERROR_FSTORE_INVSIZE));
}
}
fn flush(&mut self) -> Result<(), Error> {
self.file.flush()
}
}
impl<T: BlockHasher> StoreIO<T> for Store<T> {
fn delete_block(&mut self, index: usize) -> Result<(), Box<dyn std::error::Error>> {
if let Some(address) = self.block_addresses.get(index) {
self.file.seek(SeekFrom::Start(
*address + u64::try_from(DataHeader::<T>::delete_offset())?,
))?;
self.file.write(&DataHeader::<T>::delete_flag().to_le_bytes())?;
self.file.seek(SeekFrom::Start(0))?;
} else {
return Err(Box::new(StoreError::new(ERROR_OUTOFBOUNDS.to_string())));
}
Ok(())
}
fn block_address(&self, index: usize) -> Option<&u64> {
self.block_addresses.get(index)
}
fn len(&self) -> usize {
self.block_addresses.len()
}
fn seek(&mut self, index: usize) -> Result<u64, Box<dyn std::error::Error>> {
if let Some(a) = self.block_addresses.get(index) {
Ok(self.file.seek(SeekFrom::Start(*a))?)
} else {
return Err(Box::new(StoreError::new(ERROR_OUTOFBOUNDS.to_string())));
}
}
fn read_data_header(
&mut self,
data_header: &mut DataHeader<T>,
) -> Result<(), Box<dyn std::error::Error>> {
let mut db_buf = vec![0u8; DataHeader::<T>::size()];
self.file.read(&mut db_buf)?;
data_header.deserialize(&db_buf)?;
Ok(())
}
fn read(&mut self, data: &mut Vec<u8>) -> Result<usize, Error> {
self.file.read(data)
}
fn read_at_index(&mut self,index: usize, data: &mut Vec<u8>) -> Result<usize, Box<dyn std::error::Error>> {
if let Some(a) = self.block_addresses.get(index) {
self.file.seek(SeekFrom::Start(*a))?;
Ok(self.read(data)?)
} else {
return Err(Box::new(StoreError::new(ERROR_OUTOFBOUNDS.to_string())));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::data_header::DataHeader;
use crate::store::Store;
use crate::crypto::B3BlockHasher;
use std::io::Write;
fn fill_test_vector(data: &mut Vec<u8>) {
data.append(&mut vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 255]);
}
#[test]
fn can_write_to_store() {
let mut s = Store::<B3BlockHasher>::create("testout/store.st".to_string()).unwrap();
let mut buf = vec![0, 1, 3, 4, 5, 11, 33, 0];
s.write(&mut buf).unwrap();
s.write(&mut buf).unwrap();
}
#[test]
fn can_read_from_store() {
let mut testval = Vec::new();
fill_test_vector(&mut testval);
{
let mut s = Store::<B3BlockHasher>::create("testout/store.test.st".to_string()).unwrap();
for _i in 1..10 {
s.write(&testval).unwrap();
s.write(&testval).unwrap();
}
}
let mut db = DataHeader::<B3BlockHasher>::new().unwrap();
let mut s = Store::<B3BlockHasher>::new("testout/store.test.st".to_string()).unwrap();
s.read_data_header(&mut db).unwrap();
println!("data header size: {:?}", db);
let mut data = vec![0u8; db.data_size().unwrap()];
s.read(&mut data).unwrap();
assert_eq!(testval, data);
}
#[test]
fn can_delete_block() {
let v = [
vec!(1, 244, 231,13,42,1,2,3,4,5,6,7),
vec!(1,2,3,4,5,6,7,8,9,0),
vec!(11,12,13,14,15,16,17,18,19,20),
];
let mut s = Store::<B3BlockHasher>::create("testout/delete.tst".to_string()).unwrap();
for i in v {
s.write(&i).unwrap();
}
s.delete_block(2).unwrap();
let mut db = DataHeader::<B3BlockHasher>::new().unwrap();
s.seek(2).unwrap();
s.read_data_header(&mut db).unwrap();
assert_eq!(DataHeader::<B3BlockHasher>::delete_flag(),db.state_flag );
}
}