use std::{
fmt::{Display, Formatter},
mem,
ops::{Deref, DerefMut},
};
use thiserror::Error;
use crate::{encrypt::ecdh::PublicKey, DateTime, Magic, FORMAT_VERSION};
#[derive(Error, Clone, Debug)]
pub enum Error {
#[error("chunk overflow")]
Overflow,
}
pub(crate) struct Chunk<T>(T);
#[repr(C)]
#[derive(Clone, Debug)]
pub(crate) struct TimeRange {
start: [u8; 8],
end: [u8; 8],
}
impl TimeRange {
#[inline]
pub(crate) fn start(&self) -> DateTime {
let timestamp = i64::from_le_bytes(self.start);
DateTime::from_timestamp(timestamp, 0).unwrap_or_default()
}
#[inline]
pub(crate) fn end(&self) -> DateTime {
let timestamp = i64::from_le_bytes(self.end);
DateTime::from_timestamp(timestamp, 0).unwrap_or_default()
}
}
impl Display for TimeRange {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{} - {}", self.start(), self.end())
}
}
#[repr(C)]
#[derive(Clone, Debug)]
pub(crate) struct Header {
magic: [u8; 4],
version: [u8; 2],
length: [u8; 4],
writeback: bool,
time_range: TimeRange,
pub_key: PublicKey,
}
impl Header {
pub(crate) const LEN: usize = mem::size_of::<Self>();
const MAGIC: Magic = Magic::new(0xFEEDCA7C);
#[inline]
pub(crate) fn validate(&self) -> bool {
Self::MAGIC == self.magic.into()
}
#[inline]
pub(crate) fn version(&self) -> u16 {
u16::from_le_bytes(self.version)
}
#[inline]
pub(crate) fn payload_len(&self) -> usize {
u32::from_le_bytes(self.length) as usize
}
#[inline]
pub(crate) fn writeback(&self) -> bool {
self.writeback
}
#[inline]
pub(crate) fn time_range(&self) -> &TimeRange {
&self.time_range
}
#[inline]
pub(crate) fn pub_key(&self) -> PublicKey {
self.pub_key
}
#[inline]
pub(crate) fn bytes(self) -> [u8; Self::LEN] {
unsafe { mem::transmute(self) }
}
}
impl<T> Chunk<T>
where
T: Deref<Target = [u8]>,
{
#[inline]
pub(crate) fn bind(inner: T) -> Self {
debug_assert!(inner.len() >= Header::LEN, "the storage is too small");
Self(inner)
}
#[inline]
pub(crate) fn validate(&self) -> bool {
self.header().validate() && self.header().payload_len() <= self.capacity()
}
#[inline]
pub(crate) fn start_datetime(&self) -> DateTime {
self.header().time_range().start()
}
#[inline]
pub(crate) fn payload_len(&self) -> usize {
self.header().payload_len()
}
pub(crate) fn is_almost_full(&self) -> bool {
const RATIO: f64 = 0.8;
self.payload_len() as f64 >= RATIO * self.capacity() as f64
}
#[inline]
fn capacity(&self) -> usize {
self.0.len() - Header::LEN
}
#[inline]
fn header(&self) -> &Header {
unsafe {
let ptr = self.0.as_ptr() as *const Header;
&*ptr
}
}
}
impl<T> Chunk<T>
where
T: DerefMut<Target = [u8]>,
{
#[inline]
pub(crate) fn initialize(&mut self, datetime: DateTime, pub_key: PublicKey) {
let header = self.header_mut();
header.magic = Header::MAGIC.into();
header.version = FORMAT_VERSION.to_le_bytes();
header.length = 0u32.to_le_bytes();
header.writeback = false;
header.pub_key = pub_key;
let datetime = datetime.timestamp().to_le_bytes();
header.time_range = TimeRange { start: datetime, end: datetime };
}
#[inline]
pub(crate) fn write(&mut self, bytes: &[u8]) -> Result<(), Error> {
let old_len = self.payload_len();
let new_len = old_len + bytes.len();
if new_len > self.capacity() {
return Err(Error::Overflow);
}
let payload = self.payload_mut();
payload[old_len..new_len].copy_from_slice(bytes);
self.set_payload_len(new_len);
Ok(())
}
#[inline]
pub(crate) fn set_writeback(&mut self) {
self.header_mut().writeback = true;
}
#[inline]
pub(crate) fn set_end_datetime(&mut self, datetime: DateTime) {
self.header_mut().time_range.end = datetime.timestamp().to_le_bytes();
}
#[inline]
pub(crate) fn clear(&mut self) {
self.set_payload_len(0);
}
#[inline]
fn set_payload_len(&mut self, len: usize) {
let len: u32 = len.try_into().expect("len is too large");
self.header_mut().length = len.to_le_bytes();
}
#[inline]
fn payload_mut(&mut self) -> &mut [u8] {
&mut self.0[Header::LEN..]
}
#[inline]
fn header_mut(&mut self) -> &mut Header {
unsafe {
let ptr = self.0.as_mut_ptr() as *mut Header;
&mut *ptr
}
}
}
impl<T> Deref for Chunk<T>
where
T: Deref<Target = [u8]>,
{
type Target = [u8];
#[inline]
fn deref(&self) -> &Self::Target {
let len = self.payload_len().min(self.capacity()) + Header::LEN;
&self.0[..len]
}
}
pub(crate) use reader::{Error as ReadError, Reader};
pub(crate) mod reader {
use std::io;
use thiserror::Error;
use crate::{
chunk::Header,
common::{BytesBuf, Sink},
BUFFER_LEN,
};
#[derive(Error, Debug)]
pub(crate) enum Error {
#[error("invalid chunk")]
Invalid,
#[error(transparent)]
Io(#[from] io::Error),
#[error("unexpected end of bytes")]
UnexpectedEnd,
}
pub(crate) struct Reader<R> {
inner: R,
buffer: BytesBuf,
}
impl<R> Reader<R>
where
R: io::Read + io::Seek,
{
#[inline]
pub(crate) fn new(inner: R) -> Self {
Self { inner, buffer: BytesBuf::with_capacity(BUFFER_LEN) }
}
pub(crate) fn read_header_or_reach_to_end(&mut self) -> Result<Option<&Header>, Error> {
let buffer = self.buffer.as_buffer_mut_slice();
assert!(buffer.len() >= Header::LEN, "buffer is too small");
let mut read_len = 0;
while read_len < Header::LEN {
match self.inner.read(&mut buffer[read_len..Header::LEN]) {
Ok(0) => {
return if read_len == 0 { Ok(None) } else { Err(Error::UnexpectedEnd) }
}
Ok(len) => read_len += len,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return Err(e.into()),
}
}
let header: &Header = unsafe {
let ptr = buffer.as_ptr() as *const Header;
&*ptr
};
if header.validate() {
Ok(Some(header))
} else {
Err(Error::Invalid)
}
}
pub(crate) fn read_payload<S>(&mut self, len: usize, sink: &mut S) -> Result<(), S::Error>
where
S: Sink<Error>,
{
let buffer = self.buffer.as_buffer_mut_slice();
let mut remaining = len;
while remaining > 0 {
let capacity = buffer.len().min(remaining);
match self.inner.read(&mut buffer[..capacity]) {
Ok(0) => return Err(Error::UnexpectedEnd.into()),
Ok(len) => {
remaining -= len;
sink.sink(&buffer[..len]).inspect_err(|_| {
_ = self.inner.seek(io::SeekFrom::Current(
remaining.try_into().expect("payload is too large"),
));
})?;
}
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return Err(Error::Io(e).into()),
}
}
Ok(())
}
#[inline]
pub(crate) fn skip(&mut self, len: usize) -> Result<(), Error> {
let len: i64 = len.try_into().expect("chunk is too large");
self.inner.seek(io::SeekFrom::Current(len))?;
Ok(())
}
}
}