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 block = prepare_encrypted_object(store.block_size(), crypto, codec, kind, obj)?;
44    store.write_block(block_id, &block)
45}
46
47/// Serialize, encrypt, and pad a typed object into a block-sized buffer
48/// (without writing to store).  Returns the ready-to-write block bytes.
49pub fn prepare_encrypted_object<T: serde::Serialize>(
50    block_size: usize,
51    crypto: &dyn CryptoEngine,
52    codec: &PostcardCodec,
53    kind: ObjectKind,
54    obj: &T,
55) -> FsResult<Vec<u8>> {
56    let plaintext = codec.serialize_object(obj)?;
57    prepare_encrypted_block(block_size, crypto, codec, kind, &plaintext)
58}
59
60/// High-level helper: read a block, extract envelope, decrypt, deserialize.
61pub fn read_encrypted_object<T: serde::de::DeserializeOwned>(
62    store: &dyn BlockStore,
63    crypto: &dyn CryptoEngine,
64    codec: &PostcardCodec,
65    block_id: u64,
66) -> FsResult<T> {
67    let plaintext = decrypt_block_to_plaintext(store, crypto, codec, block_id)?;
68    codec.deserialize_object(&plaintext)
69}
70
71/// Read a block, extract envelope, decrypt, and return the raw plaintext bytes
72/// (before deserialization into a typed object).  Used by the object cache.
73pub fn decrypt_block_to_plaintext(
74    store: &dyn BlockStore,
75    crypto: &dyn CryptoEngine,
76    codec: &PostcardCodec,
77    block_id: u64,
78) -> FsResult<Vec<u8>> {
79    let block = store.read_block(block_id)?;
80
81    if block.len() < 4 {
82        return Err(FsError::Deserialization("block too small".into()));
83    }
84
85    let len = u32::from_le_bytes([block[0], block[1], block[2], block[3]]) as usize;
86    if len == 0 || 4 + len > block.len() {
87        return Err(FsError::ObjectNotFound(block_id));
88    }
89
90    let envelope_bytes = &block[4..4 + len];
91    let encrypted: EncryptedObject = codec.deserialize_object(envelope_bytes)?;
92    decrypt_object(crypto, &encrypted)
93}
94
95/// Write raw encrypted bytes (for file data chunks that are already raw bytes).
96pub fn write_encrypted_raw(
97    store: &dyn BlockStore,
98    crypto: &dyn CryptoEngine,
99    codec: &PostcardCodec,
100    block_id: u64,
101    kind: ObjectKind,
102    raw_data: &[u8],
103) -> FsResult<()> {
104    let block = prepare_encrypted_block(store.block_size(), crypto, codec, kind, raw_data)?;
105    store.write_block(block_id, &block)
106}
107
108/// Encrypt raw data and pack it into a block-sized buffer (without writing to store).
109/// Returns the ready-to-write block bytes.
110pub fn prepare_encrypted_block(
111    block_size: usize,
112    crypto: &dyn CryptoEngine,
113    codec: &PostcardCodec,
114    kind: ObjectKind,
115    raw_data: &[u8],
116) -> FsResult<Vec<u8>> {
117    let encrypted = encrypt_object(crypto, kind, raw_data)?;
118    let envelope_bytes = codec.serialize_object(&encrypted)?;
119
120    if envelope_bytes.len() > block_size {
121        return Err(FsError::DataTooLarge(envelope_bytes.len()));
122    }
123
124    let mut block = random_block(block_size);
125    let len = envelope_bytes.len() as u32;
126    block[..4].copy_from_slice(&len.to_le_bytes());
127    block[4..4 + envelope_bytes.len()].copy_from_slice(&envelope_bytes);
128
129    Ok(block)
130}
131
132/// Read and decrypt raw bytes (for file data chunks).
133pub fn read_encrypted_raw(
134    store: &dyn BlockStore,
135    crypto: &dyn CryptoEngine,
136    codec: &PostcardCodec,
137    block_id: u64,
138) -> FsResult<Vec<u8>> {
139    let block = store.read_block(block_id)?;
140
141    if block.len() < 4 {
142        return Err(FsError::Deserialization("block too small".into()));
143    }
144
145    let len = u32::from_le_bytes([block[0], block[1], block[2], block[3]]) as usize;
146    if len == 0 || 4 + len > block.len() {
147        return Err(FsError::ObjectNotFound(block_id));
148    }
149
150    let envelope_bytes = &block[4..4 + len];
151    let encrypted: EncryptedObject = codec.deserialize_object(envelope_bytes)?;
152    decrypt_object(crypto, &encrypted)
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158    use crate::block_store::MemoryBlockStore;
159    use crate::crypto::ChaChaEngine;
160
161    #[test]
162    fn test_write_read_encrypted_object() {
163        let store = MemoryBlockStore::new(4096, 16);
164        let engine = ChaChaEngine::generate().unwrap();
165        let codec = PostcardCodec;
166
167        let inode = Inode {
168            id: 42,
169            kind: InodeKind::File,
170            size: 1024,
171            directory_page_ref: ObjectRef::null(),
172            extent_map_ref: ObjectRef::new(5),
173            created_at: 1000,
174            modified_at: 2000,
175        };
176
177        write_encrypted_object(&store, &engine, &codec, 3, ObjectKind::Inode, &inode).unwrap();
178        let recovered: Inode = read_encrypted_object(&store, &engine, &codec, 3).unwrap();
179
180        assert_eq!(recovered.id, 42);
181        assert_eq!(recovered.size, 1024);
182        assert_eq!(recovered.kind, InodeKind::File);
183    }
184}