@ -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 Freeze Inner
{
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 = [ 0 u8 ; $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
///