1use std::sync::Arc;
21
22use bytes::Bytes;
23
24use super::crypto::AesGcmCipher;
25use super::key_metadata::StandardKeyMetadata;
26use super::stream::{AesGcmFileRead, AesGcmFileWrite};
27use crate::Result;
28use crate::io::{FileMetadata, FileRead, FileWrite, InputFile, OutputFile};
29
30pub struct EncryptedInputFile {
34 inner: InputFile,
35 key_metadata: StandardKeyMetadata,
36}
37
38impl EncryptedInputFile {
39 pub fn new(inner: InputFile, key_metadata: StandardKeyMetadata) -> Self {
41 Self {
42 inner,
43 key_metadata,
44 }
45 }
46
47 pub fn location(&self) -> &str {
49 self.inner.location()
50 }
51
52 pub async fn exists(&self) -> Result<bool> {
54 self.inner.exists().await
55 }
56
57 pub async fn metadata(&self) -> Result<FileMetadata> {
61 let raw_meta = self.inner.metadata().await?;
62 let plaintext_size = AesGcmFileRead::calculate_plaintext_length(raw_meta.size)?;
63 Ok(FileMetadata {
64 size: plaintext_size,
65 })
66 }
67
68 pub async fn read(&self) -> Result<Bytes> {
70 let meta = self.metadata().await?;
71 let reader = self.reader().await?;
72 reader.read(0..meta.size).await
73 }
74
75 pub async fn reader(&self) -> Result<Box<dyn FileRead>> {
77 let raw_meta = self.inner.metadata().await?;
78 let raw_reader = self.inner.reader().await?;
79 let cipher = build_cipher(&self.key_metadata)?;
80 let aad_prefix: Box<[u8]> = self.key_metadata.aad_prefix().unwrap_or_default().into();
81 let decrypting = AesGcmFileRead::new(raw_reader, cipher, aad_prefix, raw_meta.size)?;
82 Ok(Box::new(decrypting))
83 }
84
85 pub fn key_metadata(&self) -> &StandardKeyMetadata {
87 &self.key_metadata
88 }
89
90 pub fn into_inner(self) -> InputFile {
92 self.inner
93 }
94}
95
96impl std::fmt::Debug for EncryptedInputFile {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 f.debug_struct("EncryptedInputFile")
99 .field("path", &self.inner.location())
100 .finish_non_exhaustive()
101 }
102}
103
104pub struct EncryptedOutputFile {
108 inner: OutputFile,
109 key_metadata: StandardKeyMetadata,
110}
111
112impl EncryptedOutputFile {
113 pub fn new(inner: OutputFile, key_metadata: StandardKeyMetadata) -> Self {
115 Self {
116 inner,
117 key_metadata,
118 }
119 }
120
121 pub fn key_metadata(&self) -> &StandardKeyMetadata {
123 &self.key_metadata
124 }
125
126 pub fn location(&self) -> &str {
128 self.inner.location()
129 }
130
131 pub async fn writer(&self) -> Result<Box<dyn FileWrite>> {
133 let raw_writer = self.inner.writer().await?;
134 let cipher = build_cipher(&self.key_metadata)?;
135 let aad_prefix: Box<[u8]> = self.key_metadata.aad_prefix().unwrap_or_default().into();
136 Ok(Box::new(AesGcmFileWrite::new(
137 raw_writer, cipher, aad_prefix,
138 )))
139 }
140
141 pub async fn write(&self, bs: Bytes) -> Result<()> {
143 let mut writer = self.writer().await?;
144 writer.write(bs).await?;
145 writer.close().await
146 }
147
148 pub async fn delete(&self) -> Result<()> {
150 self.inner.delete().await
151 }
152
153 pub fn into_inner(self) -> OutputFile {
155 self.inner
156 }
157}
158
159impl std::fmt::Debug for EncryptedOutputFile {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 f.debug_struct("EncryptedOutputFile")
162 .field("path", &self.inner.location())
163 .finish_non_exhaustive()
164 }
165}
166
167fn build_cipher(metadata: &StandardKeyMetadata) -> Result<Arc<AesGcmCipher>> {
168 let key = metadata.encryption_key().clone();
169 Ok(Arc::new(AesGcmCipher::new(key)))
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use crate::io::FileIO;
176
177 fn key_metadata() -> StandardKeyMetadata {
178 StandardKeyMetadata::try_new(b"0123456789abcdef")
179 .unwrap()
180 .with_aad_prefix(b"test-aad-prefix!")
181 }
182
183 #[tokio::test]
184 async fn test_write_read_roundtrip() {
185 let fileio = FileIO::new_with_memory();
186 let path = "memory:///test/io_roundtrip.bin";
187 let plaintext = b"Hello from EncryptedInputFile/EncryptedOutputFile!";
188
189 let output = EncryptedOutputFile::new(fileio.new_output(path).unwrap(), key_metadata());
190 output.write(Bytes::from(plaintext.to_vec())).await.unwrap();
191
192 let input = EncryptedInputFile::new(fileio.new_input(path).unwrap(), key_metadata());
193 let content = input.read().await.unwrap();
194 assert_eq!(&content[..], plaintext);
195 }
196
197 #[tokio::test]
198 async fn test_metadata_returns_plaintext_size() {
199 let fileio = FileIO::new_with_memory();
200 let path = "memory:///test/io_metadata.bin";
201 let plaintext = b"some bytes to measure";
202
203 let output = EncryptedOutputFile::new(fileio.new_output(path).unwrap(), key_metadata());
204 output.write(Bytes::from(plaintext.to_vec())).await.unwrap();
205
206 let raw_size = fileio
207 .new_input(path)
208 .unwrap()
209 .metadata()
210 .await
211 .unwrap()
212 .size;
213 assert!(
214 raw_size > plaintext.len() as u64,
215 "encrypted file should be larger than plaintext (header + nonce + tag)"
216 );
217
218 let input = EncryptedInputFile::new(fileio.new_input(path).unwrap(), key_metadata());
219 let meta = input.metadata().await.unwrap();
220 assert_eq!(meta.size, plaintext.len() as u64);
221 }
222}