Skip to main content

doublecrypt_core/
codec.rs

1use rand::RngCore;
2
3use crate::block_store::BlockStore;
4use crate::crypto::{decrypt_object, encrypt_object, CryptoEngine};
5use crate::error::{FsError, FsResult};
6use crate::model::*;
7
8/// Create a block-sized buffer filled with random bytes.
9fn random_block(size: usize) -> Vec<u8> {
10    let mut buf = vec![0u8; size];
11    rand::thread_rng().fill_bytes(&mut buf);
12    buf
13}
14
15/// Trait for serializing and deserializing logical objects to/from bytes.
16pub trait ObjectCodec: Send + Sync {
17    fn serialize_object<T: serde::Serialize>(&self, obj: &T) -> FsResult<Vec<u8>>;
18    fn deserialize_object<T: serde::de::DeserializeOwned>(&self, bytes: &[u8]) -> FsResult<T>;
19}
20
21/// Postcard-based codec (compact binary serialization).
22pub struct PostcardCodec;
23
24impl ObjectCodec for PostcardCodec {
25    fn serialize_object<T: serde::Serialize>(&self, obj: &T) -> FsResult<Vec<u8>> {
26        postcard::to_allocvec(obj).map_err(|e| FsError::Serialization(e.to_string()))
27    }
28
29    fn deserialize_object<T: serde::de::DeserializeOwned>(&self, bytes: &[u8]) -> FsResult<T> {
30        postcard::from_bytes(bytes).map_err(|e| FsError::Deserialization(e.to_string()))
31    }
32}
33
34/// High-level helper: serialize a typed object, encrypt it, pad to block size, and write.
35pub fn write_encrypted_object<T: serde::Serialize>(
36    store: &dyn BlockStore,
37    crypto: &dyn CryptoEngine,
38    codec: &PostcardCodec,
39    block_id: u64,
40    kind: ObjectKind,
41    obj: &T,
42) -> FsResult<()> {
43    let plaintext = codec.serialize_object(obj)?;
44    let encrypted = encrypt_object(crypto, kind, &plaintext)?;
45    let envelope_bytes = codec.serialize_object(&encrypted)?;
46
47    let block_size = store.block_size();
48    if envelope_bytes.len() > block_size {
49        return Err(FsError::DataTooLarge(envelope_bytes.len()));
50    }
51
52    // Pad to block size with random bytes so padding is indistinguishable from ciphertext.
53    let mut block = random_block(block_size);
54    // First 4 bytes: little-endian length of the envelope.
55    let len = envelope_bytes.len() as u32;
56    block[..4].copy_from_slice(&len.to_le_bytes());
57    block[4..4 + envelope_bytes.len()].copy_from_slice(&envelope_bytes);
58
59    store.write_block(block_id, &block)
60}
61
62/// High-level helper: read a block, extract envelope, decrypt, deserialize.
63pub fn read_encrypted_object<T: serde::de::DeserializeOwned>(
64    store: &dyn BlockStore,
65    crypto: &dyn CryptoEngine,
66    codec: &PostcardCodec,
67    block_id: u64,
68) -> FsResult<T> {
69    let block = store.read_block(block_id)?;
70
71    if block.len() < 4 {
72        return Err(FsError::Deserialization("block too small".into()));
73    }
74
75    let len = u32::from_le_bytes([block[0], block[1], block[2], block[3]]) as usize;
76    if len == 0 || 4 + len > block.len() {
77        return Err(FsError::ObjectNotFound(block_id));
78    }
79
80    let envelope_bytes = &block[4..4 + len];
81    let encrypted: EncryptedObject = codec.deserialize_object(envelope_bytes)?;
82    let plaintext = decrypt_object(crypto, &encrypted)?;
83    codec.deserialize_object(&plaintext)
84}
85
86/// Write raw encrypted bytes (for file data chunks that are already raw bytes).
87pub fn write_encrypted_raw(
88    store: &dyn BlockStore,
89    crypto: &dyn CryptoEngine,
90    codec: &PostcardCodec,
91    block_id: u64,
92    kind: ObjectKind,
93    raw_data: &[u8],
94) -> FsResult<()> {
95    let encrypted = encrypt_object(crypto, kind, raw_data)?;
96    let envelope_bytes = codec.serialize_object(&encrypted)?;
97
98    let block_size = store.block_size();
99    if envelope_bytes.len() > block_size {
100        return Err(FsError::DataTooLarge(envelope_bytes.len()));
101    }
102
103    let mut block = random_block(block_size);
104    let len = envelope_bytes.len() as u32;
105    block[..4].copy_from_slice(&len.to_le_bytes());
106    block[4..4 + envelope_bytes.len()].copy_from_slice(&envelope_bytes);
107
108    store.write_block(block_id, &block)
109}
110
111/// Read and decrypt raw bytes (for file data chunks).
112pub fn read_encrypted_raw(
113    store: &dyn BlockStore,
114    crypto: &dyn CryptoEngine,
115    codec: &PostcardCodec,
116    block_id: u64,
117) -> FsResult<Vec<u8>> {
118    let block = store.read_block(block_id)?;
119
120    if block.len() < 4 {
121        return Err(FsError::Deserialization("block too small".into()));
122    }
123
124    let len = u32::from_le_bytes([block[0], block[1], block[2], block[3]]) as usize;
125    if len == 0 || 4 + len > block.len() {
126        return Err(FsError::ObjectNotFound(block_id));
127    }
128
129    let envelope_bytes = &block[4..4 + len];
130    let encrypted: EncryptedObject = codec.deserialize_object(envelope_bytes)?;
131    decrypt_object(crypto, &encrypted)
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137    use crate::block_store::MemoryBlockStore;
138    use crate::crypto::ChaChaEngine;
139
140    #[test]
141    fn test_write_read_encrypted_object() {
142        let store = MemoryBlockStore::new(4096, 16);
143        let engine = ChaChaEngine::generate().unwrap();
144        let codec = PostcardCodec;
145
146        let inode = Inode {
147            id: 42,
148            kind: InodeKind::File,
149            size: 1024,
150            directory_page_ref: ObjectRef::null(),
151            extent_map_ref: ObjectRef::new(5),
152            created_at: 1000,
153            modified_at: 2000,
154        };
155
156        write_encrypted_object(&store, &engine, &codec, 3, ObjectKind::Inode, &inode).unwrap();
157        let recovered: Inode = read_encrypted_object(&store, &engine, &codec, 3).unwrap();
158
159        assert_eq!(recovered.id, 42);
160        assert_eq!(recovered.size, 1024);
161        assert_eq!(recovered.kind, InodeKind::File);
162    }
163}