use crate::{
commit::CommitTree,
encoding::encoding_options,
formats::{vault_stream, EventLogFileRecord, FileItem, VaultRecord},
vfs, Error, Result,
};
use binary_stream::futures::BinaryReader;
use std::io::SeekFrom;
use tokio_util::compat::TokioAsyncReadCompatExt;
use crate::events::EventLogFile;
use std::path::Path;
macro_rules! read_iterator_item {
($record:expr, $reader:expr) => {{
let value = $record.value();
let length = value.end - value.start;
$reader.seek(SeekFrom::Start(value.start)).await?;
$reader.read_bytes(length as usize).await?
}};
}
pub async fn vault_commit_tree_file<P: AsRef<Path>, F>(
vault: P,
verify: bool,
func: F,
) -> Result<CommitTree>
where
F: Fn(&VaultRecord),
{
let mut tree = CommitTree::new();
let mut file = vfs::File::open(vault.as_ref()).await?.compat();
let mut reader = BinaryReader::new(&mut file, encoding_options());
let mut it = vault_stream(vault.as_ref()).await?;
while let Some(record) = it.next_entry().await? {
if verify {
let commit = record.commit();
let buffer = read_iterator_item!(&record, &mut reader);
let checksum = CommitTree::hash(&buffer);
if checksum != commit {
return Err(Error::HashMismatch {
commit: hex::encode(commit),
value: hex::encode(checksum),
});
}
}
func(&record);
tree.insert(record.commit());
}
tree.commit();
Ok(tree)
}
pub async fn event_log_commit_tree_file<P: AsRef<Path>, F>(
event_log_file: P,
verify: bool,
func: F,
) -> Result<CommitTree>
where
F: Fn(&EventLogFileRecord),
{
let mut tree = CommitTree::new();
let mut file = vfs::File::open(event_log_file.as_ref()).await?.compat();
let mut reader = BinaryReader::new(&mut file, encoding_options());
let event_log = EventLogFile::new(event_log_file.as_ref()).await?;
let mut it = event_log.iter().await?;
let mut last_checksum: Option<[u8; 32]> = None;
while let Some(record) = it.next_entry().await? {
if verify {
if let Some(last_checksum) = last_checksum {
let expected_last_commit = record.last_commit();
if last_checksum != expected_last_commit {
return Err(Error::HashMismatch {
commit: hex::encode(expected_last_commit),
value: hex::encode(last_checksum),
});
}
}
let commit = record.commit();
let buffer = read_iterator_item!(&record, &mut reader);
let checksum = CommitTree::hash(&buffer);
if checksum != commit {
return Err(Error::HashMismatch {
commit: hex::encode(commit),
value: hex::encode(checksum),
});
}
last_checksum = Some(record.commit());
}
func(&record);
tree.insert(record.commit());
}
tree.commit();
Ok(tree)
}
#[cfg(test)]
mod test {
use anyhow::Result;
use std::io::Write;
use tempfile::NamedTempFile;
use super::*;
use crate::{encode, test_utils::*};
#[tokio::test]
async fn integrity_empty_vault() -> Result<()> {
let (temp, _, _) = mock_vault_file().await?;
let commit_tree =
vault_commit_tree_file(temp.path(), true, |_| {}).await?;
assert!(commit_tree.root().is_none());
Ok(())
}
#[tokio::test]
async fn integrity_vault() -> Result<()> {
let (encryption_key, _, _) = mock_encryption_key()?;
let (_, mut vault, _) = mock_vault_file().await?;
let secret_label = "Test note";
let secret_note = "Super secret note for you to read.";
let (_secret_id, _commit, _, _, _) = mock_vault_note(
&mut vault,
&encryption_key,
secret_label,
secret_note,
)
.await?;
let buffer = encode(&vault).await?;
let mut temp = NamedTempFile::new()?;
temp.write_all(&buffer)?;
let commit_tree =
vault_commit_tree_file(temp.path(), true, |_| {}).await?;
assert!(commit_tree.root().is_some());
Ok(())
}
#[tokio::test]
async fn integrity_event_log() -> Result<()> {
let (temp, _, _, _) = mock_event_log_file().await?;
let commit_tree =
event_log_commit_tree_file(temp.path(), true, |_| {}).await?;
assert!(commit_tree.root().is_some());
Ok(())
}
}