start: rework freeze

new-idea
Avril 4 years ago
parent 85e6df6dc5
commit 8f3a030c6e
Signed by: flanchan
GPG Key ID: 284488987C31F630

@ -1,5 +1,4 @@
use crate::mnemonic::MnemonicSaltKind;
use std::num::NonZeroUsize;
/// Default anonymous name
pub const ANON_NAME: &'static str = "名無し";
@ -52,3 +51,8 @@ pub const MAX_IMAGE_READ_SIZE: usize = (1024 * 1024 * 1024) * 3; // 3GB
/// Must be 1 or larger.
pub const STATE_STREAM_BUFFER_SIZE: usize = 1;
static_assert!(STATE_STREAM_BUFFER_SIZE > 0);
/// The current version of the file formats for saving state
///
/// TODO: Create `Version` type that takes from `env!(version)` at compile time.
pub const VERSION: u32 = 0;

@ -1,28 +1,175 @@
//! Frozen, serialisable state
use super::*;
use cryptohelpers::sha256::{Sha256Hash, self};
//use futures::prelude::*;
//use std::io;
//use tokio::prelude::*;
use std::io;
use tokio::prelude::*;
/// An immutable image of `State`.
//TODO: Implement this when `State` is solidified and working
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Freeze
struct FreezeInner
{
users: HashSet<User>,
posts: HashSet<Post>,
}
const FREEZE_CHK: &[u8; 4] = b"REI\0";
/// Metadata derived from `FreezeInner`'s CBOR serialisation.
///
/// This is written and read as-is.
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)]
struct FreezeMetadata
{
chk: [u8; 4],
version: u32, //Version, //TODO: Convert env!(version) into type
body_size: u64,
compressed: bool, //TODO: Should body be compressed? If so, with what?
body_hash: Sha256Hash,
}
impl FreezeMetadata
{
/// Write this metadata to an async stream, return the number of bytes written.
pub async fn write_to(&self, mut to: impl tokio::io::AsyncWrite + Unpin) -> io::Result<usize>
{
macro_rules! write_all
{
($expr:expr) => {
{
let bytes = $expr;
let bytes = &bytes[..];
to.write_all(bytes).await?;
bytes.len()
}
}
}
let done =
write_all!(self.chk) +
write_all!(u32::to_le_bytes(self.version)) +
write_all!(u64::to_le_bytes(self.body_size)) +
write_all!(if self.compressed {[1]} else {[0]}) +
write_all!(self.body_hash.as_ref());
Ok(done)
}
/// Read a metadata object from an aynsc stream, without verifying any of its fields
async fn read_from_unchecked(mut from: impl tokio::io::AsyncRead + Unpin) -> io::Result<Self>
{
macro_rules! read_exact
{
($num:expr) => {
{
let mut buf = [0u8; $num];
from.read_exact(&mut buf[..]).await?;
buf
}
};
(type $type:ty) => {
{
read_exact!(std::mem::size_of::<$type>())
}
};
}
Ok(
Self {
chk: read_exact!(4),
version: u32::from_le_bytes(read_exact!(type u32)),
body_size: u64::from_le_bytes(read_exact!(type u64)),
compressed: if read_exact!(1)[0] == 0 { false } else { true },
body_hash: Sha256Hash::from(read_exact!(type Sha256Hash)),
}
)
}
/// Read a metadata object from an async stream, verifying its fields.
///
/// # Note
/// The `body_hash` field must be verified *after* reading the body.
pub async fn read_from(from: impl tokio::io::AsyncRead + Unpin) -> eyre::Result<Self>
{
macro_rules! enforce {
($expr:expr; $err:expr) => {
{
if ! $expr {
return Err($err)
}
}
}
}
let this = Self::read_from_unchecked(from).await
.wrap_err(eyre!("Failed to read data from stream"))?;
macro_rules! enforce {
($expr:expr, $err:expr) => {
{
if ! $expr {
Err(eyre!($err))
.with_section(|| format!("{:?}", this).header("Metadata was"))
} else {
Ok(())
}
}
}
}
enforce!(&this.chk == FREEZE_CHK, "Check value was invalid")
.with_section(|| format!("{:?}", FREEZE_CHK).header("Expected"))
.with_section(|| format!("{:?}", &this.chk).header("Got"))
.with_suggestion(|| "Was this the correct type of file you wanted to load?")?;
enforce!(this.body_size <= defaults::MAX_IMAGE_READ_SIZE as u64, "Body size exceeded max")
.with_section(|| format!("{}", &this.body_size).header("Size read was"))
.with_section(|| format!("{}", defaults::MAX_IMAGE_READ_SIZE).header("Max size allowed is"))
.with_warning(|| "This may indicate file corruption")?;
enforce!(this.version <= defaults::VERSION, "Unsupported version")
.with_section(|| this.version.to_string().header("Read version was"))
.with_section(|| defaults::VERSION.to_string().header("Current version is"))
.with_suggestion(|| "This file may have been created with a newer version of the program. Try updating the program.")?;
Ok(this)
}
/// Verify the hash of this metadata by computing the hash of `from` and checking.
///
/// # Notes
/// It is recommended to to this within a tokio `spawn_blocking` or `block_in_place` closure, as the hashing operation may take a while.
pub fn verify_hash_blocking(&self, from: impl AsRef<[u8]>) -> bool
{
sha256::compute_slice(from) == self.body_hash
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Freeze
{
//metadata: FreezeMetadata, //written as-is, calculated from body
//body: Vec<u8>, // `FreezeInner` written with CBOR
inner: FreezeInner, // Dumped to Vec<u8> as CBOR, then FreezeMetadata is calculated from this binary data. Then metadata is written, then the binary blob is written.
}
impl Freeze
{
/// Generate the output to write
fn gen_output(&self) -> (FreezeMetadata, Box<u8>)
{
todo!()
}
}
/*
/// Reading and writing state
//TODO: Compression
impl Freeze
{
/// Serialise this instance into an output synchronously
pub fn write_sync(&self, output: impl io::Write) -> eyre::Result<()>
{
serde_cbor::to_writer(output, self)
.wrap_err(eyre!("Failed to write (sync) to output"))
/// Serialise this instance into an output synchronously
pub fn write_sync(&self, output: impl io::Write) -> eyre::Result<()>
{
serde_cbor::to_writer(output, self)
.wrap_err(eyre!("Failed to write (sync) to output"))
}
/// Serialise this instance into an output asynchronously
///

Loading…
Cancel
Save