ouisync/protocol/
block.rs

1use crate::crypto::{Digest, Hash, Hashable};
2use rand::{distributions::Standard, prelude::Distribution, Rng};
3use serde::{Deserialize, Serialize};
4use std::{
5    array::TryFromSliceError,
6    fmt,
7    ops::{Deref, DerefMut},
8};
9use zeroize::Zeroize;
10
11/// Block size in bytes.
12pub const BLOCK_SIZE: usize = 32 * 1024;
13
14/// Size of the block db record in bytes.
15pub const BLOCK_RECORD_SIZE: u64 =
16    BLOCK_SIZE as u64 + BlockId::SIZE as u64 + BLOCK_NONCE_SIZE as u64;
17
18pub(crate) const BLOCK_NONCE_SIZE: usize = 32;
19pub(crate) type BlockNonce = [u8; BLOCK_NONCE_SIZE];
20
21/// Unique id of a block.
22#[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
23#[repr(transparent)]
24pub struct BlockId(Hash);
25
26impl BlockId {
27    pub(crate) const SIZE: usize = Hash::SIZE;
28
29    /// Computes `BlockId` from block ciphertext and nonce.
30    pub(crate) fn new(content: &BlockContent, nonce: &BlockNonce) -> Self {
31        Self((&content[..], &nonce[..]).hash())
32    }
33}
34
35impl AsRef<[u8]> for BlockId {
36    fn as_ref(&self) -> &[u8] {
37        self.0.as_ref()
38    }
39}
40
41impl TryFrom<&'_ [u8]> for BlockId {
42    type Error = TryFromSliceError;
43
44    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
45        Hash::try_from(slice).map(Self)
46    }
47}
48
49impl fmt::Display for BlockId {
50    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
51        self.0.fmt(f)
52    }
53}
54
55impl fmt::Debug for BlockId {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        self.0.fmt(f)
58    }
59}
60
61impl Hashable for BlockId {
62    fn update_hash<S: Digest>(&self, state: &mut S) {
63        self.0.update_hash(state)
64    }
65}
66
67derive_sqlx_traits_for_byte_array_wrapper!(BlockId);
68
69#[cfg(test)]
70derive_rand_for_wrapper!(BlockId);
71
72#[derive(Clone)]
73pub(crate) struct Block {
74    pub id: BlockId,
75    pub content: BlockContent,
76    pub nonce: BlockNonce,
77}
78
79impl Block {
80    pub fn new(content: BlockContent, nonce: BlockNonce) -> Self {
81        let id = BlockId::new(&content, &nonce);
82        Self { id, content, nonce }
83    }
84}
85
86impl Distribution<Block> for Standard {
87    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Block {
88        let content = rng.r#gen();
89        let nonce = rng.r#gen();
90        Block::new(content, nonce)
91    }
92}
93
94impl fmt::Debug for Block {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        f.debug_struct("Block")
97            .field("id", &self.id)
98            .finish_non_exhaustive()
99    }
100}
101
102#[derive(Clone, Serialize, Deserialize)]
103pub(crate) struct BlockContent(Box<[u8]>);
104
105impl BlockContent {
106    pub fn new() -> Self {
107        Self::default()
108    }
109
110    // Read data from `offset` of the buffer into a fixed-length array.
111    //
112    // # Panics
113    //
114    // Panics if the remaining length after `offset` is less than `N`.
115    pub fn read_array<const N: usize>(&self, offset: usize) -> [u8; N] {
116        self[offset..offset + N].try_into().unwrap()
117    }
118
119    // Read data from `offset` of the buffer into a `u64`.
120    //
121    // # Panics
122    //
123    // Panics if the remaining length is less than `size_of::<u64>()`
124    pub fn read_u64(&self, offset: usize) -> u64 {
125        u64::from_le_bytes(self.read_array(offset))
126    }
127
128    // Read data from offset into `dst`.
129    pub fn read(&self, offset: usize, dst: &mut [u8]) {
130        dst.copy_from_slice(&self.0[offset..offset + dst.len()]);
131    }
132
133    // Write a `u64` at `offset` into the buffer.
134    pub fn write_u64(&mut self, offset: usize, value: u64) {
135        let bytes = value.to_le_bytes();
136        self.write(offset, &bytes[..]);
137    }
138
139    // Writes data from `dst` into the buffer.
140    pub fn write(&mut self, offset: usize, src: &[u8]) {
141        self.0[offset..offset + src.len()].copy_from_slice(src);
142    }
143}
144
145impl Default for BlockContent {
146    fn default() -> Self {
147        Self(vec![0; BLOCK_SIZE].into_boxed_slice())
148    }
149}
150
151// Scramble the buffer on drop to prevent leaving decrypted data in memory past the buffer
152// lifetime.
153impl Drop for BlockContent {
154    fn drop(&mut self) {
155        self.0.zeroize()
156    }
157}
158
159impl Deref for BlockContent {
160    type Target = [u8];
161
162    fn deref(&self) -> &Self::Target {
163        &self.0
164    }
165}
166
167impl DerefMut for BlockContent {
168    fn deref_mut(&mut self) -> &mut Self::Target {
169        &mut self.0
170    }
171}
172
173impl Distribution<BlockContent> for Standard {
174    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> BlockContent {
175        let mut content = vec![0; BLOCK_SIZE].into_boxed_slice();
176        rng.fill(&mut content[..]);
177
178        BlockContent(content)
179    }
180}
181
182impl fmt::Debug for BlockContent {
183    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
184        write!(f, "{:<8}", hex_fmt::HexFmt(&self[..]))
185    }
186}
187
188#[cfg(test)]
189mod test_utils {
190    use super::{Block, BlockContent, BlockNonce, BLOCK_SIZE};
191    use proptest::{
192        arbitrary::{any, Arbitrary, StrategyFor},
193        collection::{vec, VecStrategy},
194        strategy::{Map, NoShrink, Strategy},
195    };
196
197    impl Arbitrary for BlockContent {
198        type Parameters = ();
199        type Strategy = Map<NoShrink<VecStrategy<StrategyFor<u8>>>, fn(Vec<u8>) -> Self>;
200
201        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
202            vec(any::<u8>(), BLOCK_SIZE)
203                .no_shrink()
204                .prop_map(|bytes| Self(bytes.into_boxed_slice()))
205        }
206    }
207
208    impl Arbitrary for Block {
209        type Parameters = ();
210        type Strategy = Map<
211            (StrategyFor<BlockContent>, NoShrink<StrategyFor<BlockNonce>>),
212            fn((BlockContent, BlockNonce)) -> Self,
213        >;
214
215        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
216            (any::<BlockContent>(), any::<BlockNonce>().no_shrink())
217                .prop_map(|(content, nonce)| Self::new(content, nonce))
218        }
219    }
220}