From d2c94d0c331edff957fa43d865269a16f1c22c8b Mon Sep 17 00:00:00 2001 From: Avril Date: Sat, 26 Sep 2020 14:18:39 +0100 Subject: [PATCH] text formatting --- src/format/key/mod.rs | 56 +++++++++++++++++++++-- src/format/mod.rs | 104 ++++++++++++++++++++++++++++++++++++------ src/main.rs | 32 ++++++++++++- src/version.rs | 59 ++++++++++++++++++++---- 4 files changed, 221 insertions(+), 30 deletions(-) diff --git a/src/format/key/mod.rs b/src/format/key/mod.rs index a2fbe9c..430ef2f 100644 --- a/src/format/key/mod.rs +++ b/src/format/key/mod.rs @@ -71,6 +71,7 @@ pub struct KeyHeader impl Header for KeyHeader { const CHECK: u16 = CHECK_KEY; + const NAME: &'static str = "KEY"; fn hash(&self) -> Sha256Hash { let mut output = Vec::new(); @@ -104,13 +105,58 @@ impl KeyHeader #[instrument(err, skip(out))] pub async fn write_text(&self, out: &mut T) -> Result { - todo!() + let text = serialise::into_text(self) + .wrap_err_with(|| eyre::eyre!("Failed to serialise header to text")) + .with_section(|| format!("{:?}", self).header("Header was"))?; + + let mut written=0; + for bytes in text.as_bytes().chunks(16) { + out.write_all(bytes).await?; + out.write_u8(b'\n').await?; + written += bytes.len() + 1; + } + out.write_all(b"\n---").await?; + Ok(written + 4) } /// Read a superheader as text bytes from this stream #[instrument(err, skip(input))] - pub async fn read_text(input: &mut T) -> Result + pub async fn read_text(input: &mut T) -> Result { - todo!() + let (mut tx, mut rx) = mpsc::channel(1); + let line_sender = async move { //this is actually kinda overkill for this... + let mut buffer = String::new(); + while input.read_line(&mut buffer).await? != 0 { + tx.send(buffer.clone()); + buffer.clear(); + } + Ok::<(), io::Error>(()) + }; + let line_reader = async { + let mut enc = String::new(); + let mut had_delim =false; + while let Some(line) = rx.recv().await { + let line = line.trim(); + if line == "---" { + had_delim=true; + break; + } else if line.len()>0 { + enc.push_str(line) + } + } + if !had_delim { + warn!("Buffer contained no end-of-entry delimiter"); + } + //let = take_one!("Expected header line"); + + Ok::(serialise::from_text(&enc[..]) + .wrap_err_with(|| eyre::eyre!("Failed to deserialise string")) + .with_section(|| enc.header("Read string was"))?) + }; + tokio::pin!(line_sender); + tokio::pin!(line_reader); + let (sres, rres) = tokio::join!(line_sender, line_reader); + sres?; + Ok(rres?) } /// Write this key header as bytes to this stream #[instrument(err, skip(out))] @@ -260,13 +306,13 @@ mod tests //color_eyre::install()?; panic!("We're going to have to write our own text serialisation to get around the `read_whole_buffer` thingy..."); -/* + /* let header = KeyHeader::new_now(KeyHeaderKind::Aes, Default::default(), Default::default()); let mut ser = Vec::new(); let superheader = SuperHeader::::new_for(&header); println!("Writing: {:?} + {:?}", superheader, header); let written = superheader.write_text(&mut ser).await? + - header.write_text(&mut ser).await?; + header.write_text(&mut ser).await?; println!("Wrote {} bytes", written); println!("{}\n", ser.fmt_view()); diff --git a/src/format/mod.rs b/src/format/mod.rs index 9b8d142..34ee4e4 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -12,24 +12,43 @@ use tokio::{ prelude::*, io::{ AsyncWrite, + AsyncBufRead, AsyncRead, }, + sync::{ + mpsc, + }, }; use version::Version; +/// Trait RAE headers implement pub trait Header: fmt::Debug { + /// Check bit for the `SuperHeader` of this header const CHECK: u16; + /// Name for the `SuperHeader` of this header's text serialisation format + const NAME: &'static str; + /// Compute the hash of this header (used for `SuperHeader`) fn hash(&self) -> crypto::sha256::Sha256Hash; - + /// Any additional comment to be inserted into *text* output formats of `SuperHeader` only + #[inline] fn comment(&self) -> &str + { + "" + } + + /// Create a `SuperHeader` for this header. + #[inline(always)] fn create_super(&self) -> SuperHeader + { + SuperHeader::new_for(self) + } } pub const RAE_HEADER_BIT: [u8; 4] = *b"RAE0"; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] /// The header before all RAE files. -pub struct SuperHeader +pub struct SuperHeader { head: [u8; 4], vers: Version, @@ -39,7 +58,7 @@ pub struct SuperHeader _header: PhantomData, } -impl Default for SuperHeader +impl Default for SuperHeader { #[inline] fn default() -> Self @@ -50,18 +69,71 @@ impl Default for SuperHeader const MAX_TEXT_SZ: Option = Some(1024 * 1024); -impl SuperHeader +impl SuperHeader { /// Write this superheader as text bytes to this stream #[instrument(err, skip(out))] pub async fn write_text(&self, out: &mut T) -> Result { - todo!() + let string = format!(r#"--- {} v{}-{:x} ({}) {} --- +"#, std::str::from_utf8(&self.head[..]) + .wrap_err_with(|| eyre::eyre!("Invalid head check write")) + .with_section(|| String::from_utf8_lossy(&self.head[..]).into_owned().header("Head string was")) + .with_section(|| self.head.fmt_view().to_string().header("Head bytes were"))?, + self.vers, + self.vers.to_u32(), + self.header_hash.to_hex_string(), + H::NAME); + out.write_all(string.as_bytes()).await?; + Ok(string.len()) } /// Read a superheader as text bytes from this stream #[instrument(err, skip(input))] - pub async fn read_text(input: &mut T) -> Result + pub async fn read_text(input: &mut T) -> Result { + let mut line = String::new(); + input.read_line(&mut line).await?; + let line = line.trim(); + trace!("Read super-header line: {:?}", line); + let mut chunks = line.split_whitespace(); + let mut item = 0; + macro_rules! take_one { + () => { + if let Some(chunk) = chunks.next() { + chunk + } else { + return Err(eyre::eyre!("Not enough information encoded for this string to be valid") + .with_section(|| format!("{:#?}", chunks).header("Split section was")) + .with_note(|| "Expect at least 6 items delimited by whitespace")); + } + } + } + macro_rules! check_eq { + ($val:expr) => { + { + let val = $val; + let chunk = take_one!(); + if chunk != val { + return Err(eyre::eyre!("Invalid data at index {}", item) + .with_section(|| format!("{:?}", val).header("Expected")) + .with_section(|| format!("{:?}", chunk).header("Got"))); + } + item +=1; + } + } + } + check_eq!("---"); + check_eq!(unsafe{std::str::from_utf8_unchecked(&RAE_HEADER_BIT[..])}); + //TODO: Parse text version from hex encoded integer in `2` + let version = { + let enc_str = take_one!(); + // Version should be encoded like: v0.0.0*-0 (v-) + todo!() + }; + //TODO: Parse hash from hex encoded string `3` + check_eq!(H::NAME); + check_eq!("---"); + debug_assert_eq!(item, 6); todo!() } @@ -131,7 +203,7 @@ impl SuperHeader } /// Verify this empty superheader - pub fn verify(&self) -> Result<(), VerificationError> + pub fn verify(&self) -> Result<(), VerificationError> { macro_rules! check { ($field:expr, $err:expr) => { @@ -142,7 +214,7 @@ impl SuperHeader } check!(self.head == RAE_HEADER_BIT, VerificationError::BadHeaderBit); check!(self.vers.is_compat(&CURRENT_VERSION), VerificationError::IncompatableVersion(self.vers)); - check!(self.chk == H::CHECK, VerificationError::BadCheckBit); + check!(self.chk == H::CHECK, VerificationError::BadCheckBit(PhantomData)); Ok(()) } @@ -151,7 +223,7 @@ impl SuperHeader /// /// # Notes /// This also calls `self.verify()`. - pub fn verify_for(&self, header: &H) -> Result<(), VerificationError> + pub fn verify_for(&self, header: &H) -> Result<(), VerificationError> { self.verify()?; if self.header_hash == header.hash() { @@ -162,25 +234,29 @@ impl SuperHeader } } +/// Error returned when `SuperHeader` fails to verify. +/// +/// `H` is the header type that we were attempting to verify for #[derive(Debug)] #[non_exhaustive] -pub enum VerificationError +pub enum VerificationError { BadHeaderBit, IncompatableVersion(Version), - BadCheckBit, + BadCheckBit(PhantomData), BadHash, } -impl error::Error for VerificationError{} -impl fmt::Display for VerificationError +impl error::Error for VerificationError{} +impl fmt::Display for VerificationError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "failed to verify header for {}: ", std::any::type_name::())?; match self { Self::BadHeaderBit => write!(f, "bad header bit"), Self::IncompatableVersion(vers) => write!(f, "contained incompatable version {}, we are on version {}", vers, CURRENT_VERSION), - Self::BadCheckBit => write!(f, "contained invalid check bit"), + Self::BadCheckBit(_) => write!(f, "contained invalid check bit"), Self::BadHash => write!(f, "contained invalid header hash"), } } diff --git a/src/main.rs b/src/main.rs index 12f0d9e..7fc0261 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,10 @@ -#![cfg_attr(nightly, feature(label_break_value))] +#![cfg_attr(nightly, feature(label_break_value))] +#![cfg_attr(nightly, feature(const_fn))] +#![cfg_attr(nightly, feature(never_type))] + #![allow(dead_code)] +#![allow(unused_macros)] #[macro_use] extern crate tracing; #[macro_use] extern crate pin_project; @@ -32,6 +36,32 @@ macro_rules! cfg_debug { } }; } +macro_rules! nightly { + (else $($else:tt)*) => { + cfg_if::cfg_if! { + if #[cfg(not(nightly))] { + $($else)* + } + } + }; + (if {$($if:tt)*} else {$($else:tt)*}) => { + + cfg_if::cfg_if!{ + if #[cfg(nightly)] { + $($if)* + } else { + $($else)* + } + } + }; + ($($tt:tt)*) => { + cfg_if::cfg_if! { + if #[cfg(nightly)] { + $($tt)* + } + } + } +} macro_rules! static_assert { ($expr:expr) => { diff --git a/src/version.rs b/src/version.rs index 7465229..b3e793d 100644 --- a/src/version.rs +++ b/src/version.rs @@ -86,7 +86,8 @@ impl Default for Tag #[repr(align(4))] pub struct Version(u8,u8,u8,Tag); - +static_assert!(std::mem::size_of::() == std::mem::size_of::<[u8; 4]>(), + "Size of version != size of [u8; 4]"); static_assert!(std::mem::size_of::() == std::mem::size_of::(), "Size of version != size of u32"); static_assert!(std::mem::size_of::() == 4, @@ -94,6 +95,7 @@ static_assert!(std::mem::size_of::() == 4, static_assert!(std::mem::align_of::() == std::mem::align_of::(), "Align of version != align of u32 with `repr(align(4))`"); + impl fmt::Display for Version { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result @@ -140,17 +142,32 @@ impl Version } /// Uncheckedly create a version from bytes - pub unsafe fn from_bytes_unchecked(bytes: [u8; 4]) -> Self + #[cfg(nightly)] + #[inline(always)] pub const unsafe fn from_bytes_unchecked(bytes: [u8; 4]) -> Self + { + std::mem::transmute(bytes) + } + + /// Uncheckedly create a version from bytes + #[cfg(not(nightly))] + #[inline(always)] pub unsafe fn from_bytes_unchecked(bytes: [u8; 4]) -> Self { let mut s = Self::default(); - assert_eq!(bytes::copy_slice(s.as_bytes_mut(), &bytes[..]), std::mem::size_of::()); + debug_assert_eq!(bytes::copy_slice(s.as_bytes_mut(), &bytes[..]), std::mem::size_of::()); s } /// Convert into 4 byte array - #[inline] pub fn into_bytes(self) -> [u8; 4] + #[inline(always)] pub const fn to_bytes(self) -> [u8; 4] { - [self.0, self.1, self.2, self.3 as u8] + nightly! { + if { + unsafe {std::mem::transmute(self)} // we already statically asserted this to be okay + } + else { + [self.0, self.1, self.2, self.3 as u8] + } + } } @@ -161,19 +178,41 @@ impl Version } /// Encode as u32 - pub fn as_u32(&self) -> u32 + #[inline] pub fn as_u32(&self) -> u32 { - debug_assert_eq!(size_of::(), size_of::()); - unsafe{*(self as *const Self as *const u32)} + nightly! { + if { + self.to_u32() + } else { + debug_assert_eq!(size_of::(), size_of::()); + unsafe{*(self as *const Self as *const u32)} + } + } } + /// Convert this `Version` directly to `u32`. + #[inline(always)] pub const fn to_u32(self) -> u32 + { + unsafe { + std::mem::transmute(self) //we have already statically asserted that this is safe + } + } + + /// Decode u32 as self, assuming `Tag` is valid. - pub unsafe fn from_u32_unchecked(from: u32) -> Self + #[cfg(nightly)] + #[inline(always)] pub const unsafe fn from_u32_unchecked(from: u32) -> Self + { + std::mem::transmute(from) + } + + #[cfg(not(nightly))] + #[inline(always)] pub unsafe fn from_u32_unchecked(from:u32) -> Self { - debug_assert_eq!(size_of::(), size_of::()); *(&from as *const u32 as *const Self) } + /// Try to parse a `u32` encoded `Version`. pub fn try_from_u32(from: u32) -> Result {