snapper_box/file/
segment.rs1use std::{
4 borrow::Cow,
5 io::{Read, Write},
6};
7
8use snafu::{ensure, OptionExt, ResultExt};
9
10use crate::{
11 crypto::{CipherText, Nonce},
12 error::{BackendError, NoData, NoDataIO, SegmentIO, SegmentLength},
13};
14
15#[derive(Debug, Hash, Clone, PartialEq, Eq)]
23pub struct Segment<'a> {
24 length: u64,
26 data: Cow<'a, [u8]>,
28}
29
30impl<'a> Segment<'a> {
31 pub fn read_borrowed(source: &'a [u8]) -> Result<Self, BackendError> {
39 ensure!(!source.is_empty(), NoData);
42 ensure!(source.len() >= 8, SegmentLength);
43 let mut length_array = [0_u8; 8];
45 length_array.copy_from_slice(&source[0..8]);
46 let length = u64::from_le_bytes(length_array);
47 let length_usize: usize = length.try_into().ok().context(SegmentLength)?;
49 let data = &source[8..];
51 ensure!(data.len() >= length_usize, SegmentLength);
52 Ok(Segment {
53 length,
54 data: Cow::Borrowed(data),
55 })
56 }
57
58 pub fn total_length(&self) -> usize {
61 8 + self.data.len()
63 }
64
65 pub fn write_ref(&self, dest: &mut [u8]) -> Result<usize, BackendError> {
71 let length = self.total_length();
73 ensure!(dest.len() >= length, SegmentLength);
74 let length_bytes = (self.data.len() as u64).to_le_bytes();
77 (&mut dest[0..8]).copy_from_slice(&length_bytes);
78 let data = &mut dest[8..];
80 data.copy_from_slice(&self.data);
81 Ok(length)
82 }
83
84 pub fn write(&self, dest: &mut impl Write) -> Result<usize, BackendError> {
90 let length = self.total_length();
92 let length_bytes = (self.data.len() as u64).to_le_bytes();
93 dest.write_all(&length_bytes).context(SegmentIO)?;
94 dest.write_all(&self.data).context(SegmentIO)?;
96 Ok(length)
97 }
98
99 pub fn new_borrowed(data: &'a [u8]) -> Self {
101 Self {
102 length: data.len().try_into().expect("Impossibly large data"),
103 data: Cow::Borrowed(data),
104 }
105 }
106
107 pub fn data(&self) -> &[u8] {
109 self.data.as_ref()
110 }
111}
112
113impl Segment<'static> {
114 pub fn read_owned(source: &mut impl Read) -> Result<Self, BackendError> {
122 let mut length_array = [0_u8; 8];
124 source.read_exact(&mut length_array).context(NoDataIO)?;
125 let length = u64::from_le_bytes(length_array);
126 let length_usize: usize = length.try_into().ok().context(SegmentLength)?;
128 let mut data = vec![0_u8; length_usize];
130 source
132 .read_exact(&mut data[0..length_usize])
133 .context(SegmentIO)?;
134 Ok(Segment {
135 length,
136 data: Cow::from(data),
137 })
138 }
139
140 pub fn new(data: impl AsRef<[u8]>) -> Self {
142 let data = data.as_ref().to_vec();
143 Self {
144 length: data.len().try_into().expect("Impossibly large data"),
145 data: Cow::from(data),
146 }
147 }
148}
149
150impl<'a> From<CipherText<'a>> for Segment<'static> {
151 fn from(x: CipherText<'a>) -> Self {
157 let mut buffer = vec![];
158 if x.compressed {
160 buffer.push(1_u8);
161 } else {
162 buffer.push(0_u8);
163 };
164 buffer.extend(&*x.nonce.0);
166 buffer.extend(&*x.hmac);
168 buffer.extend(&*x.payload);
170 Segment {
171 length: buffer.len() as u64,
172 data: buffer.into(),
173 }
174 }
175}
176
177impl<'a> TryFrom<Segment<'a>> for CipherText<'a> {
178 type Error = BackendError;
179
180 fn try_from(value: Segment<'a>) -> Result<Self, Self::Error> {
187 let mut data: &[u8] = value.data.as_ref();
188 let compressed: bool = match data[0] {
190 0_u8 => false,
191 1_u8 => true,
192 _ => return Err(BackendError::InvalidCompression),
193 };
194 data = &data[1..];
195 let mut nonce = [0_u8; 24];
197 ensure!(data.len() >= 24, SegmentLength);
198 nonce.copy_from_slice(&data[0..24]);
199 data = &data[24..];
200 let mut hmac = [0_u8; 32];
202 ensure!(data.len() >= 32, SegmentLength);
203 hmac.copy_from_slice(&data[0..32]);
204 Ok(CipherText {
205 compressed,
206 nonce: Nonce(nonce.into()),
207 hmac: hmac.into(),
208 payload: match value.data {
209 Cow::Borrowed(data) => Cow::Borrowed(&data[57..]),
210 Cow::Owned(data) => data[57..].to_vec().into(),
211 },
212 })
213 }
214}
215
216#[cfg(test)]
218mod tests {
219 use super::*;
220 use crate::crypto::{ClearText, RootKey};
221 use proptest::prelude::*;
222 use std::io::{Cursor, Seek, SeekFrom};
223 proptest! {
224 #[test]
226 fn borrowed_round_trip(bytes: Vec<u8>) {
227 let segment = Segment::new_borrowed(&bytes);
229 let mut cursor = Cursor::new(Vec::<u8>::new());
231 segment.write(&mut cursor).expect("Failed to write to cursor");
232 let total_length = segment.total_length();
234 let mut buffer = vec![0_u8; total_length];
235 segment.write_ref(&mut buffer[0..total_length]).expect("Failed to write to buffer");
236 let cursor_buff = cursor.into_inner();
238 let cursor_segment = Segment::read_borrowed(&cursor_buff[..])
239 .expect("Failed to read cursor segment");
240 assert_eq!(cursor_segment, segment);
241 let buffer_segment = Segment::read_borrowed(&buffer[..])
243 .expect("Failed to read buffer segment");
244 assert_eq!(buffer_segment, segment);
245 }
246 #[test]
248 fn borrowed_owned(bytes: Vec<u8>) {
249 let segment = Segment::new(&bytes);
251 let mut cursor = Cursor::new(Vec::<u8>::new());
253 segment.write(&mut cursor).expect("Failed to write to cursor");
254 cursor.seek(SeekFrom::Start(0)).unwrap();
256 let total_length = segment.total_length();
258 let mut buffer = vec![0_u8; total_length];
259 segment.write_ref(&mut buffer[0..total_length]).expect("Failed to write to buffer");
260 let cursor_segment = Segment::read_owned(&mut cursor)
262 .expect("Failed to read cursor segment");
263 assert_eq!(cursor_segment, segment);
264 let buffer_segment = Segment::read_borrowed(&buffer[..])
266 .expect("Failed to read buffer segment");
267 assert_eq!(buffer_segment, segment);
268
269 }
270 }
271 #[test]
273 fn cipher_text_round_trip() -> Result<(), BackendError> {
274 let root_key = RootKey::random();
276 let data = vec![1_u8; 256];
277 let plaintext = ClearText::new(&data)?;
278 let ciphertext = plaintext.clone().encrypt(&root_key, None)?;
279 let segment: Segment<'_> = ciphertext.clone().into();
281 let recovered: CipherText<'_> = segment.try_into()?;
283 assert_eq!(recovered, ciphertext);
284 let recovered_plaintext = recovered.decrypt(&root_key)?;
286 assert_eq!(recovered_plaintext.payload, plaintext.payload);
287 let recovered_data: Vec<u8> = recovered_plaintext.deserialize()?;
289 assert_eq!(recovered_data, data);
290
291 Ok(())
292 }
293 #[test]
295 fn cipher_text_round_trip_compress() -> Result<(), BackendError> {
296 let root_key = RootKey::random();
298 let data = vec![1_u8; 256];
299 let plaintext = ClearText::new(&data)?;
300 let ciphertext = plaintext.clone().encrypt(&root_key, Some(1))?;
301 let segment: Segment<'_> = ciphertext.clone().into();
303 let recovered: CipherText<'_> = segment.try_into()?;
305 assert_eq!(recovered, ciphertext);
306 let recovered_plaintext = recovered.decrypt(&root_key)?;
308 assert_eq!(recovered_plaintext.payload, plaintext.payload);
309 let recovered_data: Vec<u8> = recovered_plaintext.deserialize()?;
311 assert_eq!(recovered_data, data);
312
313 Ok(())
314 }
315}