#![allow(clippy::used_underscore_binding)]
use crate::repository::{Chunk, ChunkHeader, ChunkID, ChunkSettings, EncryptedKey, Key};
use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
use chrono::{DateTime, FixedOffset};
use semver::Version;
use serde::{Deserialize, Serialize};
use serde_cbor as cbor;
use thiserror::Error;
use uuid::Uuid;
use std::collections::HashMap;
use std::convert::TryInto;
use std::io::{Read, Write};
pub const MAGIC_NUMBER: [u8; 8] = *b"ASURAN_F";
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum FlatFileError {
#[error("General I/O Error: {0}")]
IOError(#[from] std::io::Error),
#[error("Configuration Encode/Decode Error: {0}")]
Decode(#[from] cbor::error::Error),
#[error("Unable to encode key in u16::MAX bytes")]
KeyTooLong,
#[error("Magic number was not correct for Asuran FlatFile format")]
InvalidMagicNumber,
#[error("Semver component {0} too high: {1}")]
SemverToHigh(u64, Version),
#[error("Chunk decryption failed: {0}")]
ChunkError(#[from] crate::repository::chunk::ChunkError),
}
type Result<T> = std::result::Result<T, FlatFileError>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FlatFileHeader {
pub magic_number: [u8; 8],
pub length: u16,
pub enc_key: Vec<u8>,
}
impl FlatFileHeader {
pub fn new(key: &EncryptedKey) -> Result<FlatFileHeader> {
let enc_key = cbor::ser::to_vec(key).expect(
"Encrypted key does not have any types that should fail to serialize.\
This should never fail.",
);
let length: u16 = enc_key
.len()
.try_into()
.map_err(|_| FlatFileError::KeyTooLong)?;
Ok(FlatFileHeader {
magic_number: MAGIC_NUMBER,
length,
enc_key,
})
}
pub fn verify_magic_number(&self) -> bool {
self.magic_number == MAGIC_NUMBER
}
pub fn key(&self) -> Result<EncryptedKey> {
let enc_key = cbor::de::from_slice(&self.enc_key[..])?;
Ok(enc_key)
}
pub fn from_read(mut read: impl Read) -> Result<FlatFileHeader> {
let mut magic_number = [0_u8; 8];
read.read_exact(&mut magic_number)?;
let length: u16 = read.read_u16::<NetworkEndian>()?;
let mut enc_key = vec![0_u8; length as usize];
read.read_exact(&mut enc_key[..])?;
let header = FlatFileHeader {
magic_number,
length,
enc_key,
};
if !header.verify_magic_number() {
return Err(FlatFileError::InvalidMagicNumber);
}
Ok(header)
}
pub fn to_write(&self, mut write: impl Write) -> Result<()> {
write.write_all(&MAGIC_NUMBER)?;
write.write_u16::<NetworkEndian>(self.length)?;
write.write_all(&self.enc_key[..])?;
Ok(())
}
pub fn total_length(&self) -> u64 {
u64::from(self.length) + 10
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct EntryHeader {
pub semver_major: u16,
pub semver_minor: u16,
pub semver_patch: u16,
pub footer_offset: u64,
pub next_header_offset: u64,
pub uuid_bytes: [u8; 16],
}
impl EntryHeader {
pub fn new(
version: &Version,
footer_offset: u64,
next_header_offset: u64,
uuid: Uuid,
) -> Result<EntryHeader> {
let semver_major: u16 = version
.major
.try_into()
.map_err(|_| FlatFileError::SemverToHigh(version.major, version.clone()))?;
let semver_minor: u16 = version
.minor
.try_into()
.map_err(|_| FlatFileError::SemverToHigh(version.minor, version.clone()))?;
let semver_patch: u16 = version
.patch
.try_into()
.map_err(|_| FlatFileError::SemverToHigh(version.patch, version.clone()))?;
Ok(EntryHeader {
semver_major,
semver_minor,
semver_patch,
footer_offset,
next_header_offset,
uuid_bytes: *uuid.as_bytes(),
})
}
pub fn version(&self) -> Version {
Version::new(
u64::from(self.semver_major),
u64::from(self.semver_minor),
u64::from(self.semver_patch),
)
}
pub fn uuid(&self) -> Uuid {
Uuid::from_bytes(self.uuid_bytes)
}
pub fn from_read(mut read: impl Read) -> Result<EntryHeader> {
let semver_major = read.read_u16::<NetworkEndian>()?;
let semver_minor = read.read_u16::<NetworkEndian>()?;
let semver_patch = read.read_u16::<NetworkEndian>()?;
let footer_offset = read.read_u64::<NetworkEndian>()?;
let next_header_offset = read.read_u64::<NetworkEndian>()?;
let mut uuid_bytes = [0_u8; 16];
read.read_exact(&mut uuid_bytes[..])?;
Ok(EntryHeader {
semver_major,
semver_minor,
semver_patch,
footer_offset,
next_header_offset,
uuid_bytes,
})
}
pub fn to_write(&self, mut write: impl Write) -> Result<()> {
write.write_u16::<NetworkEndian>(self.semver_major)?;
write.write_u16::<NetworkEndian>(self.semver_minor)?;
write.write_u16::<NetworkEndian>(self.semver_patch)?;
write.write_u64::<NetworkEndian>(self.footer_offset)?;
write.write_u64::<NetworkEndian>(self.next_header_offset)?;
write.write_all(&self.uuid_bytes[..])?;
Ok(())
}
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EntryFooterData {
pub chunk_locations: Vec<(ChunkID, u64, u64)>,
pub chunk_headers: HashMap<ChunkID, ChunkHeader>,
pub archives: Vec<(ChunkID, DateTime<FixedOffset>)>,
pub chunk_settings: ChunkSettings,
}
impl EntryFooterData {
pub fn new(chunk_settings: ChunkSettings) -> EntryFooterData {
EntryFooterData {
chunk_locations: Vec::new(),
archives: Vec::new(),
chunk_settings,
chunk_headers: HashMap::new(),
}
}
pub fn add_chunk(&mut self, id: ChunkID, location: u64, length: u64) {
self.chunk_locations.push((id, location, length));
}
pub fn add_header(&mut self, id: ChunkID, header: ChunkHeader) {
self.chunk_headers.insert(id, header);
}
pub fn add_archive(&mut self, id: ChunkID, timestamp: DateTime<FixedOffset>) {
self.archives.push((id, timestamp))
}
pub fn dirty(&self) -> bool {
!self.chunk_locations.is_empty()
|| !self.chunk_headers.is_empty()
|| !self.archives.is_empty()
}
}
pub struct EntryFooter {
chunk_bytes: Vec<u8>,
}
impl EntryFooter {
pub fn from_data(
data: &EntryFooterData,
key: &Key,
chunk_settings: ChunkSettings,
) -> EntryFooter {
let data = cbor::ser::to_vec(data).expect(
"EntryFooterData contains no types for which serialization can fail.\
This should, realistically, never happen.",
);
let chunk = Chunk::pack(
data,
chunk_settings.compression,
chunk_settings.encryption,
chunk_settings.hmac,
key,
);
let chunk_bytes = cbor::ser::to_vec(&chunk).expect(
"Chunk contains no types for which serialization can fail.\
This should, realistically, never happen.",
);
EntryFooter { chunk_bytes }
}
pub fn into_data(self, key: &Key) -> Result<EntryFooterData> {
let chunk: Chunk = cbor::de::from_slice(&self.chunk_bytes[..])?;
let bytes = chunk.unpack(key)?;
let data: EntryFooterData = cbor::de::from_slice(&bytes[..])?;
Ok(data)
}
pub fn from_read(mut read: impl Read) -> Result<EntryFooter> {
let length = read.read_u64::<NetworkEndian>()?;
let buffer_len: usize = length
.try_into()
.expect("EntryFooter chunk too large to possibly fit in memory.");
let mut chunk_bytes = vec![0_u8; buffer_len];
read.read_exact(&mut chunk_bytes[..])?;
Ok(EntryFooter { chunk_bytes })
}
pub fn to_write(&self, mut write: impl Write) -> Result<()> {
write.write_u64::<NetworkEndian>(self.chunk_bytes.len() as u64)?;
write.write_all(&self.chunk_bytes[..])?;
Ok(())
}
}