use std::collections::HashMap;
use std::path::{Path, PathBuf};
use crate::invoice::signature::{
KeyRing, SecretKeyEntry, SecretKeyFile, SecretKeyStorage, SignatureRole,
};
use crate::provider::embedded::EmbeddedProvider;
use crate::provider::file::FileProvider;
use crate::search::StrictEngine;
use crate::signature::{KeyRingLoader, LabelMatch};
use sha2::{Digest, Sha256};
use tempfile::tempdir;
const SCAFFOLD_DIR: &str = "tests/scaffolds";
const INVOICE_FILE: &str = "invoice.toml";
const PARCEL_DIR: &str = "parcels";
const PARCEL_EXTENSION: &str = "dat";
const KEYRING_FILE: &str = "keyring.toml";
const SECRETS_FILE: &str = "secret_keys.toml";
const KEYS_DIR: &str = "keys";
pub const SCAFFOLD_DIR_ENV: &str = "BINDLE_SCAFFOLD_DIR";
fn default_scaffold_dir() -> PathBuf {
let root = std::env::var("CARGO_MANIFEST_DIR").expect("Unable to get project directory");
let mut path = PathBuf::from(root);
path.push(SCAFFOLD_DIR);
path
}
fn scaffold_dir() -> PathBuf {
std::env::var(SCAFFOLD_DIR_ENV)
.ok()
.map(PathBuf::from)
.unwrap_or_else(default_scaffold_dir)
}
#[derive(Clone, Debug)]
pub struct ParcelInfo {
pub sha: String,
pub data: Vec<u8>,
}
#[derive(Clone, Debug)]
pub struct RawScaffold {
pub invoice: Vec<u8>,
pub parcel_files: HashMap<String, ParcelInfo>,
pub keys: SecretKeyFile,
pub keyring: KeyRing,
}
impl RawScaffold {
pub async fn load(name: &str) -> RawScaffold {
let dir = scaffold_dir().join(name);
if !dir.is_dir() {
panic!("Path {} does not exist or isn't a directory", dir.display());
}
let invoice = tokio::fs::read(dir.join(INVOICE_FILE))
.await
.expect("unable to read invoice file");
let keys_dir = scaffold_dir().join(KEYS_DIR);
tokio::fs::metadata(&keys_dir)
.await
.expect("Unable to find keys directory");
let keys = SecretKeyFile::load_file(keys_dir.join(SECRETS_FILE))
.await
.expect("Unable to load secret keys file");
let keyring = keys_dir
.join(KEYRING_FILE)
.load()
.await
.expect("Unable to load keyring file");
let files = match filter_files(&dir).await {
Some(s) => s,
None => {
return RawScaffold {
invoice,
parcel_files: HashMap::new(),
keys,
keyring,
}
}
};
let file_futures = files.into_iter().map(|file| async move {
let file_name = file
.file_stem()
.expect("got unrecognized file, this is likely a programmer error")
.to_string_lossy()
.to_string();
let file_data = tokio::fs::read(&file)
.await
.expect("Unable to read parcel file");
match file.extension().unwrap_or_default().to_str().unwrap() {
PARCEL_EXTENSION => (
file_name,
ParcelInfo {
sha: format!("{:x}", Sha256::digest(&file_data)),
data: file_data,
},
),
_ => panic!("Found unknown extension, this is likely a programmer error"),
}
});
RawScaffold {
invoice,
parcel_files: futures::future::join_all(file_futures)
.await
.into_iter()
.collect(),
keys,
keyring,
}
}
}
impl From<Scaffold> for RawScaffold {
fn from(s: Scaffold) -> RawScaffold {
let invoice = toml::to_vec(&s.invoice).expect("Reserialization shouldn't fail");
RawScaffold {
invoice,
parcel_files: s.parcel_files,
keys: s.keys,
keyring: s.keyring,
}
}
}
#[derive(Clone, Debug)]
pub struct Scaffold {
pub invoice: crate::Invoice,
pub parcel_files: HashMap<String, ParcelInfo>,
pub keys: SecretKeyFile,
pub keyring: KeyRing,
}
impl Scaffold {
pub async fn load(name: &str) -> Scaffold {
let raw = RawScaffold::load(name).await;
raw.into()
}
}
impl From<RawScaffold> for Scaffold {
fn from(raw: RawScaffold) -> Scaffold {
let invoice: crate::Invoice =
toml::from_slice(&raw.invoice).expect("Unable to deserialize invoice TOML");
Scaffold {
invoice,
parcel_files: raw.parcel_files,
keys: raw.keys,
keyring: raw.keyring,
}
}
}
pub async fn setup() -> (FileProvider<StrictEngine>, StrictEngine, MockKeyStore) {
let temp = tempdir().expect("unable to create tempdir");
let index = StrictEngine::default();
let store = FileProvider::new(temp.path(), index.clone()).await;
let kstore = MockKeyStore::new();
(store, index, kstore)
}
pub async fn setup_embedded() -> (EmbeddedProvider<StrictEngine>, StrictEngine, MockKeyStore) {
let temp = tempdir().expect("unable to create tempdir");
let index = StrictEngine::default();
let store = EmbeddedProvider::new(temp.path(), index.clone())
.await
.expect("Unable to configure embedded provider");
let kstore = MockKeyStore::new();
(store, index, kstore)
}
pub async fn load_all_files() -> HashMap<String, RawScaffold> {
let dirs = bindle_dirs().await;
let dir_futures = dirs.into_iter().map(|dir| async move {
let dir_name = dir
.file_name()
.expect("got unrecognized directory, this is likely a programmer error")
.to_string_lossy()
.to_string();
let raw = RawScaffold::load(&dir_name).await;
(dir_name, raw)
});
futures::future::join_all(dir_futures)
.await
.into_iter()
.collect()
}
async fn filter_files<P: AsRef<Path>>(root_path: P) -> Option<Vec<PathBuf>> {
let mut readdir = match tokio::fs::read_dir(root_path.as_ref().join(PARCEL_DIR)).await {
Ok(r) => r,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return None,
Err(e) => panic!("unable to read parcel directory: {:?}", e),
};
let mut files = Vec::new();
while let Some(entry) = readdir
.next_entry()
.await
.expect("unable to read parcel directory")
{
let path = entry.path();
if path.extension().unwrap_or_default() == PARCEL_EXTENSION {
files.push(path);
}
}
Some(files)
}
async fn bindle_dirs() -> Vec<PathBuf> {
let mut readdir = tokio::fs::read_dir(scaffold_dir())
.await
.expect("unable to read scaffolds directory");
let mut directories = Vec::new();
while let Some(entry) = readdir
.next_entry()
.await
.expect("unable to read scaffold directory")
{
let path = entry.path();
if path.is_dir() && path.join("invoice.toml").is_file() {
directories.push(path);
}
}
directories
}
#[derive(Clone)]
pub struct MockKeyStore {
mock_secret_key: SecretKeyEntry,
}
impl MockKeyStore {
pub fn new() -> Self {
Self::default()
}
}
impl Default for MockKeyStore {
fn default() -> Self {
MockKeyStore {
mock_secret_key: SecretKeyEntry::new(
"Test <test@example.com>",
vec![SignatureRole::Host],
),
}
}
}
impl SecretKeyStorage for MockKeyStore {
fn get_first_matching(
&self,
_role: &SignatureRole,
_match_type: Option<&LabelMatch>,
) -> Option<&SecretKeyEntry> {
Some(&self.mock_secret_key)
}
fn get_all_matching(
&self,
_role: &SignatureRole,
_match_type: Option<&LabelMatch>,
) -> Vec<&SecretKeyEntry> {
vec![&self.mock_secret_key]
}
}