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