ddcp 0.2.4

Distributed decentralized database-to-database copy
use std::array::TryFromSliceError;

use veilid_core::{
    CryptoSystemVersion, Nonce, TypedKey, TypedKeyPair, VeilidAPIError, NONCE_LENGTH,
};

use crate::proto::codec;

use codec::{Decodable, Encodable};

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("{0}")]
    Proto(#[from] codec::Error),
    #[error("{0}")]
    VeilidAPI(#[from] VeilidAPIError),
    #[error("{0}")]
    DecodeFailure(#[from] TryFromSliceError),
}
pub type Result<T> = std::result::Result<T, Error>;

pub struct Crypto {
    crypto_system: CryptoSystemVersion,
    sender_key_pair: TypedKeyPair,
    recipient_public_key: TypedKey,
}

impl Crypto {
    pub fn new(
        crypto_system: CryptoSystemVersion,
        sender_key_pair: TypedKeyPair,
        recipient_public_key: TypedKey,
    ) -> Crypto {
        Crypto {
            crypto_system,
            sender_key_pair,
            recipient_public_key,
        }
    }

    pub fn encode<T: Encodable>(&self, item: T) -> Result<Vec<u8>> {
        let mut message = item.encode()?;
        let shared_key = self.crypto_system.cached_dh(
            &self.recipient_public_key.value,
            &self.sender_key_pair.value.secret,
        )?;
        let nonce = self.crypto_system.random_nonce();
        self.crypto_system
            .encrypt_in_place_aead(&mut message, &nonce, &shared_key, None)?;
        Ok(vec![nonce.to_vec(), message].concat())
    }

    pub fn decode<T: Decodable>(&self, message: &[u8]) -> Result<T> {
        let nonce = Nonce::new(message[0..NONCE_LENGTH].try_into()?);
        let mut message = message[NONCE_LENGTH..].to_vec();
        let shared_key = self.crypto_system.cached_dh(
            &self.recipient_public_key.value,
            &self.sender_key_pair.value.secret,
        )?;
        self.crypto_system
            .decrypt_in_place_aead(&mut message, &nonce, &shared_key, None)?;
        let result = T::decode(message.as_slice())?;
        Ok(result)
    }
}

#[cfg(test)]
mod tests {
    use veilid_core::{CryptoTyped, CRYPTO_KIND_VLD0};

    use crate::proto::codec::Request;
    use crate::tests::api::{setup_api, teardown_api, TEST_API_MUTEX};

    use super::*;

    #[tokio::test]
    async fn roundtrip() {
        let _lock = TEST_API_MUTEX.lock().expect("lock");

        let api = setup_api().await;
        let crypto_system = api
            .crypto()
            .expect("crypto")
            .get(CRYPTO_KIND_VLD0)
            .expect("vld0");

        let alice_keypair = CryptoTyped {
            kind: CRYPTO_KIND_VLD0,
            value: crypto_system.generate_keypair(),
        };
        let bob_keypair = CryptoTyped {
            kind: CRYPTO_KIND_VLD0,
            value: crypto_system.generate_keypair(),
        };

        let alice_crypto = Crypto::new(
            crypto_system.clone(),
            alice_keypair,
            CryptoTyped {
                kind: bob_keypair.kind,
                value: bob_keypair.value.key,
            },
        );
        let bob_crypto = Crypto::new(
            crypto_system.clone(),
            bob_keypair,
            CryptoTyped {
                kind: alice_keypair.kind,
                value: alice_keypair.value.key,
            },
        );

        let msg_bytes = alice_crypto.encode(Request::Status).expect("encode");
        let decoded = bob_crypto.decode::<Request>(&msg_bytes).expect("ok");
        assert_eq!(Request::Status, decoded);
        teardown_api(api).await;
    }
}