Skip to main content

ps_datachunk/serialized/
mod.rs

1use std::ops::Deref;
2
3use bytes::Bytes;
4use ps_buffer::{Buffer, SharedBuffer};
5use ps_hash::{hash, Hash, HASH_SIZE};
6
7use crate::{DataChunk, DataChunkError, EncryptedDataChunk, Result};
8
9#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
10pub struct SerializedDataChunk {
11    buffer: Buffer,
12    hash: Hash,
13}
14
15impl SerializedDataChunk {
16    #[must_use]
17    pub const fn data_length(&self) -> usize {
18        self.buffer.len().saturating_sub(HASH_SIZE)
19    }
20
21    #[must_use]
22    pub const fn is_empty(&self) -> bool {
23        self.buffer.len() <= HASH_SIZE
24    }
25
26    /// # Safety
27    ///
28    /// Called guarantees that `hash` is `hash(data)`
29    ///
30    /// This method does **NOT** verify `hash`!
31    ///
32    /// Call only if `hash` is surely known.
33    pub fn from_parts_unchecked<D>(data: D, hash: Hash) -> Result<Self>
34    where
35        D: AsRef<[u8]>,
36    {
37        let data = data.as_ref();
38        let buffer_length = HASH_SIZE + data.len();
39
40        let mut buffer = Buffer::with_capacity(buffer_length)?;
41
42        buffer.extend_from_slice(hash.to_string())?;
43        buffer.extend_from_slice(data)?;
44
45        let chunk = Self { buffer, hash };
46
47        Ok(chunk)
48    }
49
50    /// Allocate a `SerializedDataChunk` containing `data`
51    pub fn from_data<D>(data: D) -> Result<Self>
52    where
53        D: AsRef<[u8]>,
54    {
55        let data = data.as_ref();
56
57        Self::from_parts_unchecked(data, hash(data)?)
58    }
59
60    /// Returns a reference to this [`SerializedDataChunk`]'s serialized bytes
61    #[inline]
62    #[must_use]
63    pub fn serialized_bytes(&self) -> &[u8] {
64        &self.buffer
65    }
66
67    /// Constructs a `SerializedDataChunk` from a serialized buffer.
68    ///
69    /// `buffer` is validated to be interpretable as a `SerializedDataChunk`,
70    /// and its `hash` is recalculated and verified. However, other things,
71    /// such as padding and buffer length, are not validated.
72    pub fn from_serialized_buffer(buffer: Buffer) -> Result<Self> {
73        if buffer.len() < HASH_SIZE {
74            return Err(DataChunkError::InvalidLayout);
75        }
76
77        let hash = &buffer[..HASH_SIZE];
78        let data = &buffer[HASH_SIZE..];
79        let calculated_hash = ps_hash::hash(data)?;
80
81        if hash != calculated_hash.to_string().as_bytes() {
82            return Err(DataChunkError::HashMismatch);
83        }
84
85        let chunk = Self {
86            buffer,
87            hash: calculated_hash,
88        };
89
90        Ok(chunk)
91    }
92
93    #[inline]
94    /// extracts the serialized `Buffer` from this `SerializedDataChunk`
95    pub fn into_buffer(self) -> Buffer {
96        self.buffer
97    }
98
99    #[inline]
100    /// extracts the serialized `Buffer` and `Hash` from this `SerializedDataChunk`
101    pub fn into_parts(self) -> (Buffer, Hash) {
102        (self.buffer, self.hash)
103    }
104}
105
106impl DataChunk for SerializedDataChunk {
107    fn data_ref(&self) -> &[u8] {
108        &self.buffer[HASH_SIZE..]
109    }
110
111    fn encrypt(&self) -> Result<EncryptedDataChunk> {
112        Ok(ps_cypher::encrypt(&self.buffer)?.into())
113    }
114
115    fn hash_ref(&self) -> &Hash {
116        &self.hash
117    }
118
119    /// Transforms this [`DataChunk`] into [`Bytes`].
120    fn into_bytes(self) -> Bytes {
121        Bytes::from_owner(SharedBuffer::from(self.buffer)).slice(HASH_SIZE..)
122    }
123
124    /// Transforms this chunk into an [`crate::OwnedDataChunk`]
125    fn into_owned(self) -> crate::OwnedDataChunk {
126        let hash = self.hash();
127
128        crate::OwnedDataChunk::from_data_and_hash_unchecked(self, hash)
129    }
130}
131
132impl AsRef<[u8]> for SerializedDataChunk {
133    fn as_ref(&self) -> &[u8] {
134        self
135    }
136}
137
138impl Deref for SerializedDataChunk {
139    type Target = [u8];
140
141    fn deref(&self) -> &Self::Target {
142        self.data_ref()
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149    use crate::{DataChunk, OwnedDataChunk};
150
151    #[test]
152    fn from_parts_unchecked_accepts_matching_hash() -> Result<()> {
153        let data = b"hello world";
154        let hash = ps_hash::hash(data)?;
155
156        let chunk = SerializedDataChunk::from_parts_unchecked(data, hash)?;
157
158        assert_eq!(chunk.data_ref(), data);
159
160        Ok(())
161    }
162
163    #[test]
164    fn from_parts_unchecked_accepts_mismatched_hash() -> Result<()> {
165        let data = b"hello world";
166        let mismatched_hash = ps_hash::hash(b"other bytes")?;
167
168        let chunk = SerializedDataChunk::from_parts_unchecked(data, mismatched_hash)?;
169
170        assert_eq!(chunk.data_ref(), data);
171        assert_eq!(chunk.hash(), mismatched_hash);
172
173        Ok(())
174    }
175
176    #[test]
177    fn serialize_owned_chunk_roundtrip_data() -> Result<()> {
178        let original_data = vec![1, 2, 3, 4, 5];
179        let hash = ps_hash::hash(&original_data)?;
180        let data_chunk = OwnedDataChunk::from_data_and_hash_unchecked(original_data.clone(), hash);
181
182        let serialized = data_chunk.serialize()?;
183
184        assert_eq!(serialized.data_ref(), original_data);
185
186        Ok(())
187    }
188}