You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
354 lines
11 KiB
354 lines
11 KiB
//! Handles file formats
|
|
use super::*;
|
|
use std::{
|
|
error,
|
|
fmt,
|
|
marker::{
|
|
PhantomData,
|
|
Unpin,
|
|
},
|
|
};
|
|
use tokio::{
|
|
prelude::*,
|
|
io::{
|
|
AsyncWrite,
|
|
AsyncBufRead,
|
|
AsyncRead,
|
|
},
|
|
sync::{
|
|
mpsc,
|
|
},
|
|
};
|
|
use version::Version;
|
|
use crypto::password::{Password,Salt};
|
|
|
|
/// 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<Self>`)
|
|
fn hash(&self) -> crypto::sha256::Sha256Hash;
|
|
/// Any additional comment to be inserted into *text* output formats of `SuperHeader<Self>` only
|
|
#[inline] fn comment(&self) -> &str
|
|
{
|
|
""
|
|
}
|
|
|
|
/// Create a `SuperHeader<Self>` for this header.
|
|
#[inline(always)] fn create_super(&self) -> SuperHeader<Self>
|
|
{
|
|
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<H: Header+?Sized>
|
|
{
|
|
head: [u8; 4],
|
|
vers: Version,
|
|
chk: u16,
|
|
header_hash: crypto::sha256::Sha256Hash,
|
|
|
|
_header: PhantomData<H>,
|
|
}
|
|
|
|
impl<H: Header+?Sized> Default for SuperHeader<H>
|
|
{
|
|
#[inline]
|
|
fn default() -> Self
|
|
{
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
const MAX_TEXT_SZ: Option<usize> = Some(1024 * 1024);
|
|
|
|
impl<H: Header +?Sized> SuperHeader<H>
|
|
{
|
|
/// Write this superheader as text bytes to this stream
|
|
#[instrument(err, skip(out))]
|
|
pub async fn write_text<T: AsyncWrite+Unpin+?Sized>(&self, out: &mut T, passwd: Option<&Password>) -> Result<usize, eyre::Report>
|
|
{
|
|
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, passwd))]
|
|
pub async fn read_text<T: AsyncBufRead+Unpin+?Sized, F: FnOnce(&Salt) -> Option<Password>>(input: &mut T, passwd: F) -> Result<Self, eyre::Report>
|
|
{
|
|
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();
|
|
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 chunk {}", chunk)
|
|
.with_section(|| format!("{:?}", val).header("Expected"))
|
|
.with_section(|| format!("{:?}", chunk).header("Got")));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
check_eq!("---");
|
|
check_eq!(unsafe{std::str::from_utf8_unchecked(&RAE_HEADER_BIT[..])});
|
|
let version = {
|
|
let enc_str = take_one!();
|
|
// Version should be encoded like: v0.0.0*-0 (v<version string>-<version number (hex)>)
|
|
let mut spl = enc_str.split('-').fuse();
|
|
match (spl.next(), spl.next()) {
|
|
(None, _) => Err(eyre::eyre!("Cannot extract version")),
|
|
(Some(x), None) => Err(eyre::eyre!("Cannot extract version integer")
|
|
.with_section(|| x.to_owned().header("Version part was"))),
|
|
(Some(v), Some(i)) => {
|
|
trace!("Found version string part {} and integer part {}", v, i);
|
|
|
|
match u32::from_str_radix(i, 16) {
|
|
Ok(i) => Version::try_from_u32(i)
|
|
.wrap_err_with(|| eyre::eyre!("Failed to convert integer part to version"))
|
|
.with_section(|| i.to_string().header("Integer (decoeed u32) was")),
|
|
Err(x) => Err(x).wrap_err_with(|| eyre::eyre!("Failed to decode string part into integer")),
|
|
}.with_section(|| i.to_owned().header("Integer part was"))
|
|
.with_section(|| v.to_owned().header("Version part was"))
|
|
.and_then(|vers| {
|
|
let vstr = vers.to_string();
|
|
if v.len() < 1 {
|
|
Err(eyre::eyre!("Embedded version string was invalid"))
|
|
.with_section(|| v.to_owned().header("Embedded was"))
|
|
.with_section(move || vstr.header("Decoded was"))
|
|
}
|
|
else if vstr != &v[1..] {
|
|
Err(eyre::eyre!("Embedded version string does not match decoded version string"))
|
|
.with_section(|| v.to_owned().header("Embedded was"))
|
|
.with_section(move || vstr.header("Decoded was"))
|
|
} else {
|
|
Ok(vers)
|
|
}
|
|
})
|
|
},
|
|
}.wrap_err_with(|| eyre::eyre!("Failed to decode version"))
|
|
.with_section(|| enc_str.to_owned().header("The version string was"))?
|
|
};
|
|
//Parse hash from hex encoded string `3`
|
|
let hash = {
|
|
let hash_str= take_one!();
|
|
let w = || {
|
|
if hash_str.len() < 2 {
|
|
return Err(eyre::eyre!("Not long enough"))
|
|
}
|
|
let hash_str = &hash_str[1..(hash_str.len()-1)];
|
|
use hex::FromHex;
|
|
|
|
let bytes = <[u8; crypto::consts::SHA256_SIZE]>::from_hex(hash_str)
|
|
.wrap_err_with(|| eyre::eyre!("Hex decode to {} size byte array failed", crypto::consts::SHA256_SIZE))
|
|
.with_section(|| hash_str.to_owned().header("Hash string (cut) was"))?;
|
|
|
|
Ok(unsafe{std::mem::transmute::<_, crypto::sha256::Sha256Hash>(bytes)}) //Sha256Hash is repr(transparent)
|
|
};
|
|
w()
|
|
.wrap_err_with(|| eyre::eyre!("Failed to decode hash"))
|
|
.with_section(|| hash_str.to_owned().header("The hash string was"))?
|
|
};
|
|
check_eq!(H::NAME);
|
|
check_eq!("---");
|
|
if version.should_warn(&CURRENT_VERSION) {
|
|
warn!("Header is read to have deprecated version {}, we are on version {}", version, CURRENT_VERSION)
|
|
}
|
|
Ok(
|
|
Self {
|
|
head: RAE_HEADER_BIT,
|
|
vers: version,
|
|
header_hash: hash,
|
|
chk: H::CHECK,
|
|
_header: PhantomData,
|
|
}
|
|
)
|
|
}
|
|
|
|
/// Write this superheader as bytes to this stream
|
|
#[instrument(err, skip(out))]
|
|
pub async fn write_bytes<T: AsyncWrite+Unpin+?Sized>(&self, out: &mut T, passwd: Option<&Password>) -> Result<usize, eyre::Report>
|
|
{
|
|
Ok({out.write_all(&self.head[..]).await?; self.head.len()} +
|
|
{out.write_all(self.vers.as_bytes()).await?; std::mem::size_of::<Version>()} +
|
|
{out.write_u16(self.chk).await?; 2} +
|
|
{out.write_all(self.header_hash.as_ref()).await?; std::mem::size_of::<crypto::sha256::Sha256Hash>()})
|
|
}
|
|
/// Read a superheader as bytes from this stream
|
|
#[instrument(err, skip(input, passwd))]
|
|
pub async fn read_bytes<T: AsyncRead+Unpin+?Sized, F: FnOnce(&Salt) -> Option<Password>>(input: &mut T, passwd: F) -> Result<Self, eyre::Report>
|
|
{
|
|
let mut new = Self::new();
|
|
input.read_exact(&mut new.head[..]).await?;
|
|
new.vers = {
|
|
let mut bytes = [0u8; std::mem::size_of::<Version>()];
|
|
input.read_exact(&mut bytes[..]).await?;
|
|
Version::try_from_bytes(bytes)
|
|
.wrap_err_with(|| eyre::eyre!("Failed to decode version"))
|
|
.with_section(|| bytes.to_broken_hex_string().header("Bytes (hex) were"))?
|
|
};
|
|
new.chk = input.read_u16().await?;
|
|
new.header_hash = {
|
|
let mut bytes= crypto::sha256::Sha256Hash::empty();
|
|
input.read_exact(bytes.as_mut()).await?;
|
|
bytes
|
|
};
|
|
if new.vers.should_warn(&CURRENT_VERSION) {
|
|
warn!("Header ({:?}) is read to have deprecated version {}, we are on version {}", new, new.vers, CURRENT_VERSION)
|
|
}
|
|
Ok(new)
|
|
}
|
|
|
|
/// Create a new empty superheader
|
|
pub fn new() -> Self
|
|
{
|
|
Self{
|
|
head: RAE_HEADER_BIT,
|
|
vers: CURRENT_VERSION,
|
|
chk: H::CHECK,
|
|
header_hash: Default::default(),
|
|
_header: PhantomData,
|
|
}
|
|
}
|
|
|
|
/// Consume into a new instance with hash for `header`.
|
|
pub fn with_hash(self, header: &H) -> Self
|
|
{
|
|
|
|
Self {
|
|
header_hash: header.hash(),
|
|
..self
|
|
}
|
|
}
|
|
|
|
/// Create a new superheader for
|
|
pub fn new_for(header: &H) -> Self
|
|
{
|
|
Self {
|
|
header_hash: header.hash(),
|
|
..Self::new()
|
|
}
|
|
}
|
|
|
|
/// Verify this empty superheader
|
|
pub fn verify(&self) -> Result<(), VerificationError<H>>
|
|
{
|
|
macro_rules! check {
|
|
($field:expr, $err:expr) => {
|
|
if !$field {
|
|
return Err($err);
|
|
}
|
|
}
|
|
}
|
|
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(PhantomData));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Verify this not-empty header for `header`.
|
|
///
|
|
/// # Notes
|
|
/// This also calls `self.verify()`.
|
|
pub fn verify_for(&self, header: &H) -> Result<(), VerificationError<H>>
|
|
{
|
|
self.verify()?;
|
|
if self.header_hash == header.hash() {
|
|
Ok(())
|
|
} else {
|
|
Err(VerificationError::BadHash)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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<H: Header+?Sized>
|
|
{
|
|
BadHeaderBit,
|
|
IncompatableVersion(Version),
|
|
BadCheckBit(PhantomData<H>),
|
|
BadHash,
|
|
}
|
|
|
|
impl<H: Header+?Sized> error::Error for VerificationError<H>{}
|
|
impl<H: Header+?Sized> fmt::Display for VerificationError<H>
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
{
|
|
write!(f, "failed to verify header for {}: ", std::any::type_name::<H>())?;
|
|
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::BadHash => write!(f, "contained invalid header hash"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<H: Header+?Sized> serialise::BinarySerialisable for SuperHeader<H>
|
|
{
|
|
#[inline(always)] fn serialise_bytes<'a, 'b: 'a, T: AsyncWrite+Unpin+?Sized>(&'a self, out: &'b mut T, passwd: Option<&'b Password>) -> LocalBoxFuture<'a, Result<usize, eyre::Report>>
|
|
{
|
|
self.write_bytes(out, passwd).boxed_local()
|
|
}
|
|
#[inline(always)] fn deserialise_bytes<'b , T: AsyncRead+Unpin+?Sized, F: for<'r> FnOnce(&'r Salt) -> Option<Password> + 'b>(input: &'b mut T, passwd: F) -> LocalBoxFuture<'b, Result<Self, eyre::Report>>
|
|
where Self: 'b
|
|
{
|
|
Self::read_bytes(input, passwd).boxed_local()
|
|
}
|
|
}
|
|
|
|
impl<H: Header+?Sized> serialise::TextSerialiseable for SuperHeader<H>
|
|
{
|
|
#[inline(always)] fn serialise_text<'a, 'b: 'a, T: AsyncWrite+Unpin+?Sized>(&'a self, out: &'b mut T, passwd: Option<&'b Password>) -> LocalBoxFuture<'a, Result<usize, eyre::Report>>
|
|
{
|
|
self.write_text(out, passwd).boxed_local()
|
|
}
|
|
#[inline(always)] fn deserialise_text<'a, T: AsyncBufRead+Unpin+?Sized, F: for<'r> FnOnce(&'r Salt) -> Option<Password> + 'a>(input: &'a mut T, passwd: F) -> LocalBoxFuture<'a, Result<Self, eyre::Report>>
|
|
where Self: 'a
|
|
{
|
|
Self::read_text(input, passwd).boxed_local()
|
|
}
|
|
}
|
|
|
|
/// Nopassword text constant
|
|
const TEXT_NOPASS: &[u8; 16] = b"0000000000000000";
|
|
pub mod key;
|
|
const CHECK_KEY: u16 = 0x0001;
|