sett 0.3.0

Rust port of sett (data compression, encryption and transfer tool).
Documentation
use std::{collections::BTreeMap, path::PathBuf};

use chrono::Utc;
use sett::{
    decrypt::{decrypt, DecryptOpts},
    encrypt::{encrypt, EncryptOpts},
    openpgp::{certstore::CertStore, keystore::KeyStore},
    package::{Package, CHECKSUM_FILE, CONTENT_FOLDER},
    task::Mode,
    utils::Progress,
};

const PASSWORD: &[u8] = b"secret";

struct ProgressNoop;

impl Progress for ProgressNoop {
    fn set_length(&mut self, _len: u64) {}
    fn inc(&mut self, _delta: u64) {}
    fn finish(&mut self) {}
}

async fn encrypt_password_prompt(
    _hint: sett::openpgp::types::PasswordHint,
) -> sett::openpgp::types::Password {
    PASSWORD.into()
}

fn decrypt_password_prompt(
    _hint: sett::openpgp::types::PasswordHint,
) -> sett::openpgp::types::Password {
    PASSWORD.into()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn roundtrip() {
        encrypt_and_decrypt(BTreeMap::from([
            ("foo".into(), "bar".into()),
            ("baz".into(), "qux".into()),
        ]))
        .await;
        encrypt_and_decrypt(Default::default()).await;
    }

    async fn encrypt_and_decrypt(extra_metadata: BTreeMap<String, String>) {
        const PASSWORD: &[u8] = b"secret";
        const MESSAGE: &[u8] = b"A very secret message!";
        let mut cert_store = CertStore::open_ephemeral();
        let mut key_store = KeyStore::open_ephemeral().await.unwrap();
        let (cert, _rev) = sett::openpgp::cert::CertBuilder::new()
            .add_userid("Chuck <chuck@example.org>")
            .set_password(Some(PASSWORD.into()))
            .generate()
            .unwrap();
        cert_store.import(&cert).unwrap();
        key_store.import(cert.clone()).await.unwrap();
        let tmp_dir = tempfile::tempdir().unwrap();
        let test_file = tmp_dir.path().join("test.txt");
        std::fs::write(&test_file, MESSAGE).unwrap();
        let status = encrypt(
            EncryptOpts {
                files: vec![test_file.to_path_buf()],
                recipients: vec![cert.fingerprint()],
                signer: cert.fingerprint(),
                cert_store,
                key_store,
                password: encrypt_password_prompt,
                compression_algorithm: Default::default(),
                mode: Mode::Run,
                progress: None::<ProgressNoop>,
                purpose: None,
                transfer_id: None,
                timestamp: Utc::now(),
                prefix: None,
                extra_metadata: extra_metadata.clone(),
            },
            sett::destination::Local::new(tmp_dir.path(), None::<String>)
                .unwrap()
                .into(),
        )
        .await
        .unwrap();
        let package_path = match status {
            sett::task::Status::Completed { destination, .. } => destination,
            _ => panic!("Expected completed status"),
        };

        let mut cert_store = CertStore::open_ephemeral();
        cert_store.import(&cert).unwrap();
        let mut key_store = KeyStore::open_ephemeral().await.unwrap();
        key_store.import(cert.clone()).await.unwrap();
        let decrypt_opts = DecryptOpts {
            package: Package::open(package_path).await.unwrap(),
            key_store,
            cert_store,
            password: decrypt_password_prompt,
            output: Some(tmp_dir.path().to_path_buf()),
            mode: Mode::Run,
            decrypt_only: false,
            progress: None::<ProgressNoop>,
        };
        let package = decrypt_opts
            .package
            .clone()
            .verify(&decrypt_opts.cert_store)
            .await
            .unwrap();
        let pkg_metadata = package.metadata().await.unwrap();
        let status = decrypt(decrypt_opts).await.unwrap();
        let unpacked_package = match status {
            sett::task::Status::Completed { destination, .. } => PathBuf::from(destination),
            _ => panic!("Expected completed status"),
        };
        assert!(unpacked_package.join(CHECKSUM_FILE).exists());
        let decrypted_file_path = unpacked_package.join(CONTENT_FOLDER).join("test.txt");
        assert_eq!(std::fs::read(decrypted_file_path).unwrap(), MESSAGE);
        assert_eq!(pkg_metadata.extra, extra_metadata);
    }
}