tx2-pack 0.1.1

Binary world snapshot format for ECS persistence, checkpointing, and time-travel
Documentation
#[cfg(feature = "encryption")]
use aes_gcm::{
    aead::{Aead, KeyInit, OsRng},
    Aes256Gcm, Nonce,
};

use crate::error::{PackError, Result};

#[cfg(feature = "encryption")]
#[derive(Clone)]
pub struct EncryptionKey {
    key: [u8; 32],
}

#[cfg(feature = "encryption")]
impl EncryptionKey {
    pub fn new(key: [u8; 32]) -> Self {
        Self { key }
    }

    pub fn generate() -> Self {
        use aes_gcm::aead::rand_core::RngCore;
        let mut key = [0u8; 32];
        OsRng.fill_bytes(&mut key);
        Self { key }
    }

    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
        if bytes.len() != 32 {
            return Err(PackError::Encryption(
                "Key must be exactly 32 bytes".to_string()
            ));
        }
        let mut key = [0u8; 32];
        key.copy_from_slice(bytes);
        Ok(Self { key })
    }

    pub fn as_bytes(&self) -> &[u8; 32] {
        &self.key
    }
}

#[cfg(feature = "encryption")]
pub fn encrypt_snapshot(data: &[u8], key: &EncryptionKey) -> Result<Vec<u8>> {
    use aes_gcm::aead::rand_core::RngCore;

    let cipher = Aes256Gcm::new_from_slice(&key.key)
        .map_err(|e| PackError::Encryption(e.to_string()))?;

    let mut nonce_bytes = [0u8; 12];
    OsRng.fill_bytes(&mut nonce_bytes);
    let nonce = Nonce::from_slice(&nonce_bytes);

    let ciphertext = cipher
        .encrypt(nonce, data)
        .map_err(|e| PackError::Encryption(e.to_string()))?;

    let mut result = Vec::with_capacity(12 + ciphertext.len());
    result.extend_from_slice(&nonce_bytes);
    result.extend_from_slice(&ciphertext);

    Ok(result)
}

#[cfg(feature = "encryption")]
pub fn decrypt_snapshot(data: &[u8], key: &EncryptionKey) -> Result<Vec<u8>> {
    if data.len() < 12 {
        return Err(PackError::Decryption(
            "Encrypted data too short".to_string()
        ));
    }

    let cipher = Aes256Gcm::new_from_slice(&key.key)
        .map_err(|e| PackError::Decryption(e.to_string()))?;

    let nonce = Nonce::from_slice(&data[0..12]);
    let ciphertext = &data[12..];

    let plaintext = cipher
        .decrypt(nonce, ciphertext)
        .map_err(|e| PackError::Decryption(e.to_string()))?;

    Ok(plaintext)
}

#[cfg(not(feature = "encryption"))]
pub struct EncryptionKey;

#[cfg(not(feature = "encryption"))]
impl EncryptionKey {
    pub fn new(_key: [u8; 32]) -> Self {
        Self
    }

    pub fn generate() -> Self {
        Self
    }
}

#[cfg(not(feature = "encryption"))]
pub fn encrypt_snapshot(_data: &[u8], _key: &EncryptionKey) -> Result<Vec<u8>> {
    Err(PackError::Encryption(
        "Encryption feature not enabled".to_string()
    ))
}

#[cfg(not(feature = "encryption"))]
pub fn decrypt_snapshot(_data: &[u8], _key: &EncryptionKey) -> Result<Vec<u8>> {
    Err(PackError::Decryption(
        "Encryption feature not enabled".to_string()
    ))
}

#[cfg(all(test, feature = "encryption"))]
mod tests {
    use super::*;

    #[test]
    fn test_encryption_decryption() {
        let data = b"Hello, World! This is sensitive data.";
        let key = EncryptionKey::generate();

        let encrypted = encrypt_snapshot(data, &key).unwrap();
        assert_ne!(data.as_slice(), encrypted.as_slice());

        let decrypted = decrypt_snapshot(&encrypted, &key).unwrap();
        assert_eq!(data.as_slice(), decrypted.as_slice());
    }

    #[test]
    fn test_wrong_key() {
        let data = b"Hello, World!";
        let key1 = EncryptionKey::generate();
        let key2 = EncryptionKey::generate();

        let encrypted = encrypt_snapshot(data, &key1).unwrap();
        let result = decrypt_snapshot(&encrypted, &key2);

        assert!(result.is_err());
    }
}