use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use crc::{Crc, CRC_32_ISO_HDLC};
use serde::{de::DeserializeOwned, Serialize};
use std::{
fs::{File, OpenOptions},
io::{ErrorKind, Read, Seek, SeekFrom, Write},
num::TryFromIntError,
path::Path,
};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DatabaseError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("data corruption encountered ({expected:08x} != {saved:08x})")]
MismatchedChecksum { saved: u32, expected: u32 },
#[error("inserted data too large (document > u32::MAX)")]
DataTooLarge(#[from] TryFromIntError),
#[error(transparent)]
SerdeError(#[from] bincode::Error),
}
const CRC: Crc<u32> = Crc::<u32>::new(&CRC_32_ISO_HDLC);
pub struct Client<T: Serialize + DeserializeOwned> {
file: File,
_phantom: std::marker::PhantomData<T>,
}
impl<T> Client<T>
where
T: Serialize + DeserializeOwned,
{
pub fn new<P: AsRef<Path>>(path: P, append: bool) -> Result<Self, DatabaseError> {
let file = if append {
OpenOptions::new()
.read(true)
.create(true)
.append(true)
.open(path.as_ref())?
} else {
OpenOptions::new()
.read(true)
.create(true)
.write(true)
.truncate(true)
.open(path.as_ref())?
};
Ok(Self {
file,
_phantom: std::marker::PhantomData::default(),
})
}
pub fn load(&mut self) -> Result<Option<Vec<T>>, DatabaseError> {
let mut buf = Vec::new();
self.file.seek(SeekFrom::Start(0))?;
self.file.read_to_end(&mut buf)?;
if buf.is_empty() {
return Ok(None);
}
let result = binary_to_vec(&buf)?;
Ok(Some(result))
}
pub fn validate(&mut self) -> Result<bool, DatabaseError> {
let mut buf = Vec::new();
self.file.seek(SeekFrom::Start(0))?;
self.file.read_to_end(&mut buf)?;
loop {
match process_document(&mut buf.as_slice()) {
Ok(_) => (),
Err(DatabaseError::MismatchedChecksum { .. }) => return Ok(false),
Err(DatabaseError::Io(e)) if e.kind() == ErrorKind::UnexpectedEof => break,
Err(e) => return Err(e),
}
}
Ok(true)
}
pub fn write_many(&mut self, documents: &[T]) -> Result<(), DatabaseError> {
let buf = vec_to_binary(documents)?;
self.file.seek(SeekFrom::End(0))?;
self.file.write_all(&buf)?;
Ok(())
}
pub fn write(&mut self, document: &T) -> Result<(), DatabaseError> {
let buf = vec_to_binary(std::array::from_ref(document))?;
self.file.seek(SeekFrom::End(0))?;
self.file.write_all(&buf)?;
Ok(())
}
}
fn binary_to_vec<T: DeserializeOwned>(mut raw_data: &[u8]) -> Result<Vec<T>, DatabaseError> {
let mut result = Vec::new();
loop {
let raw_doc = process_document(&mut raw_data);
let raw_doc = match raw_doc {
Ok(d) => d,
Err(e) => match e {
DatabaseError::Io(e) if e.kind() == ErrorKind::UnexpectedEof => {
break;
}
_ => return Err(e),
},
};
let data = bincode::deserialize(&raw_doc)?;
result.push(data);
}
Ok(result)
}
fn process_document<R: Read>(f: &mut R) -> Result<Vec<u8>, DatabaseError> {
let saved_checksum = f.read_u32::<LittleEndian>()?;
let data_len = f.read_u32::<LittleEndian>()?;
let mut data = Vec::with_capacity(data_len as usize);
f.take(u64::from(data_len)).read_to_end(&mut data)?;
let expected_checksum = CRC.checksum(&data);
if expected_checksum != saved_checksum {
return Err(DatabaseError::MismatchedChecksum {
saved: saved_checksum,
expected: expected_checksum,
});
}
Ok(data)
}
fn vec_to_binary<T: Serialize>(data: &[T]) -> Result<Vec<u8>, DatabaseError> {
let mut buf = Vec::new();
for document in data {
let raw_data = bincode::serialize(&document)?;
let data_len = raw_data.len();
let checksum = CRC.checksum(&raw_data);
buf.write_u32::<LittleEndian>(checksum)?;
buf.write_u32::<LittleEndian>(u32::try_from(data_len)?)?;
buf.write_all(&raw_data)?;
}
Ok(buf)
}
#[cfg(test)]
mod tests {
use crate::{binary_to_vec, vec_to_binary};
use serde_derive::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
struct Test {
id: usize,
message: String,
}
fn generate_test_data() -> Vec<Test> {
let test1 = Test {
id: 1,
message: "Hello there, you suck".to_string(),
};
let test2 = Test {
id: 2,
message: "No you".to_string(),
};
let test3 = Test {
id: 3,
message: "You both suck".to_string(),
};
vec![test1, test2, test3]
}
#[test]
fn binary_vec_conversion() {
let test_messages = generate_test_data();
let binary = vec_to_binary(&test_messages).unwrap();
let vec: Vec<Test> = binary_to_vec(&binary).unwrap();
assert_eq!(test_messages, vec);
}
}