citadel_crypt/secure_buffer/partitioned_sec_buffer.rs
1//! Partitioned Secure Buffer Implementation
2//!
3//! This module provides a secure buffer implementation that supports fixed-size
4//! partitioning for efficient memory management and data isolation. Each partition
5//! maintains its own boundaries while sharing a single underlying buffer.
6//!
7//! # Features
8//!
9//! - Generic over number of partitions
10//! - Zero-copy partition access
11//! - Boundary-checked partition operations
12//! - Memory-safe partition windows
13//! - Automatic buffer zeroing
14//!
15//! # Examples
16//!
17//! ```rust
18//! use citadel_crypt::secure_buffer::partitioned_sec_buffer::PartitionedSecBuffer;
19//!
20//! // Create a buffer with 2 partitions
21//! let mut buffer = PartitionedSecBuffer::<2>::new().unwrap();
22//!
23//! // Reserve space in partitions
24//! buffer.reserve_partition(0, 32).unwrap(); // 32 bytes for first partition
25//! buffer.reserve_partition(1, 64).unwrap(); // 64 bytes for second partition
26//!
27//! // Access partition windows
28//! let mut window = buffer.partition_window(0).unwrap();
29//! window.copy_from_slice(&[0u8; 32]); // Write to first partition
30//! ```
31//!
32//! # Important Notes
33//!
34//! - Partitions must be reserved in order
35//! - Partition boundaries are strictly enforced
36//! - Buffer is automatically zeroed on drop
37//! - Windows provide safe partition access
38//!
39//! # Related Components
40//!
41//! - [`SecBuffer`] - Underlying secure buffer
42//! - [`crate::secure_buffer::sec_packet`] - Packet buffer implementation
43//!
44
45use bytes::{BufMut, BytesMut};
46use citadel_types::crypto::SecBuffer;
47use serde::{Deserialize, Serialize};
48use serde_big_array::BigArray;
49use std::ops::{Deref, DerefMut, Range};
50
51/// A secure buffer implementation with N fixed-size partitions
52///
53/// The buffer is divided into N partitions, each with its own size and boundaries.
54/// Partitions must be reserved in order and provide safe, isolated access to their
55/// respective memory regions.
56///
57/// # Type Parameters
58///
59/// * `N` - Number of partitions in the buffer
60///
61/// # Fields
62///
63/// * `layout` - Array storing the size of each partition
64/// * `buffer` - Underlying secure buffer storing all partition data
65#[derive(Debug, Serialize, Deserialize)]
66pub struct PartitionedSecBuffer<const N: usize> {
67 #[serde(with = "BigArray")]
68 layout: [u32; N],
69 buffer: SecBuffer,
70}
71
72impl<const N: usize> PartitionedSecBuffer<N> {
73 /// Creates a new partitioned secure buffer with N partitions
74 ///
75 /// # Errors
76 ///
77 /// Returns an error if the number of partitions is zero
78 pub fn new() -> std::io::Result<Self> {
79 if N != 0 {
80 Ok(Self {
81 layout: [0; N],
82 buffer: SecBuffer::empty(),
83 })
84 } else {
85 Err(std::io::Error::new(
86 std::io::ErrorKind::InvalidInput,
87 "Partitions == 0",
88 ))
89 }
90 }
91
92 /// Reserves space for a partition at the specified index
93 ///
94 /// # Parameters
95 ///
96 /// * `idx` - Index of the partition to reserve
97 /// * `len` - Size of the partition in bytes
98 ///
99 /// # Errors
100 ///
101 /// Returns an error if the index is out of bounds, the partition is already reserved,
102 /// or if previous partitions have not been reserved
103 pub fn reserve_partition(&mut self, idx: usize, len: u32) -> std::io::Result<()> {
104 self.check_reserve(idx)?;
105 self.buffer.handle().put_bytes(0, len as _);
106 self.layout[idx] = len;
107
108 Ok(())
109 }
110
111 /// Returns a window to the partition slice at the specified index
112 ///
113 /// # Parameters
114 ///
115 /// * `idx` - Index of the partition to access
116 ///
117 /// # Errors
118 ///
119 /// Returns an error if the index is out of bounds
120 pub fn partition_window(&mut self, idx: usize) -> std::io::Result<SliceHandle> {
121 let range = self.get_range(idx)?;
122 Ok(SliceHandle {
123 ptr: &mut self.buffer,
124 range,
125 })
126 }
127
128 /// Returns the range of the partition at the specified index
129 ///
130 /// # Parameters
131 ///
132 /// * `idx` - Index of the partition to access
133 ///
134 /// # Errors
135 ///
136 /// Returns an error if the index is out of bounds
137 fn get_range(&self, idx: usize) -> std::io::Result<Range<usize>> {
138 self.check_index(idx)?;
139 let start_idx = self.layout.iter().take(idx).copied().sum::<u32>() as usize; // at 0, we get 0. At 1, we get the sum of the first partition width
140 let end_idx = if idx + 1 == N {
141 // this is the final partition. End index is the length
142 self.buffer.len()
143 } else {
144 // this is not the final partition. Take the start index, and add to it the length of the partition at idx
145 start_idx + self.layout[idx] as usize
146 };
147
148 Ok(start_idx..end_idx)
149 }
150
151 /// Checks if the index is within bounds
152 ///
153 /// # Parameters
154 ///
155 /// * `idx` - Index to check
156 ///
157 /// # Errors
158 ///
159 /// Returns an error if the index is out of bounds
160 fn check_index(&self, idx: usize) -> std::io::Result<()> {
161 if idx >= N {
162 Err(std::io::Error::new(
163 std::io::ErrorKind::InvalidInput,
164 "idx > partitions",
165 ))
166 } else {
167 Ok(())
168 }
169 }
170
171 /// Checks if the partition can be reserved
172 ///
173 /// # Parameters
174 ///
175 /// * `idx` - Index of the partition to reserve
176 ///
177 /// # Errors
178 ///
179 /// Returns an error if the index is out of bounds, the partition is already reserved,
180 /// or if previous partitions have not been reserved
181 fn check_reserve(&self, idx: usize) -> std::io::Result<()> {
182 self.check_index(idx)?;
183 // make sure current value is unset
184 if self.layout[idx] != 0 {
185 return Err(std::io::Error::new(
186 std::io::ErrorKind::InvalidInput,
187 "Current index already set",
188 ));
189 }
190
191 // make sure every index before idx has a nonzero value
192 for idx in 0..idx {
193 if self.layout[idx] == 0 {
194 return Err(std::io::Error::new(
195 std::io::ErrorKind::InvalidInput,
196 "Previously unset partition detected",
197 ));
198 }
199 }
200
201 // make sure every index after idx has a zero value
202 for idx in idx..N {
203 if self.layout[idx] != 0 {
204 return Err(std::io::Error::new(
205 std::io::ErrorKind::InvalidInput,
206 "Next partitions already set",
207 ));
208 }
209 }
210
211 Ok(())
212 }
213
214 /// Consumes the buffer and returns the underlying bytes
215 pub fn into_buffer(self) -> BytesMut {
216 self.buffer.into_buffer()
217 }
218
219 /// Returns a reference to the partition layout
220 pub fn layout(&self) -> &[u32; N] {
221 &self.layout
222 }
223}
224
225/// A handle to a partition slice
226pub struct SliceHandle<'a> {
227 pub(crate) range: Range<usize>,
228 ptr: &'a mut SecBuffer,
229}
230
231impl Deref for SliceHandle<'_> {
232 type Target = [u8];
233
234 fn deref(&self) -> &Self::Target {
235 &self.ptr.as_ref()[self.range.clone()]
236 }
237}
238
239impl DerefMut for SliceHandle<'_> {
240 fn deref_mut(&mut self) -> &mut Self::Target {
241 &mut self.ptr.as_mut()[self.range.clone()]
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use crate::secure_buffer::partitioned_sec_buffer::PartitionedSecBuffer;
248
249 #[test]
250 #[should_panic]
251 fn partitioned_sec_buffer_0() {
252 citadel_logging::should_panic_test();
253 let _ = PartitionedSecBuffer::<0>::new().unwrap();
254 }
255
256 #[test]
257 fn partitioned_sec_buffer_1_proper() {
258 citadel_logging::setup_log();
259 let mut buf = PartitionedSecBuffer::<1>::new().unwrap();
260 buf.reserve_partition(0, 10).unwrap();
261 buf.partition_window(0).unwrap().fill(1);
262 assert_eq!(buf.into_buffer(), &vec![1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
263 }
264
265 #[test]
266 #[should_panic]
267 fn partitioned_sec_buffer_1_improper() {
268 citadel_logging::should_panic_test();
269 let mut buf = PartitionedSecBuffer::<1>::new().unwrap();
270 buf.reserve_partition(1, 10).unwrap();
271 }
272
273 #[test]
274 #[should_panic]
275 fn partitioned_sec_buffer_1_improper_2() {
276 citadel_logging::should_panic_test();
277 let mut buf = PartitionedSecBuffer::<1>::new().unwrap();
278 buf.reserve_partition(0, 10).unwrap();
279 buf.partition_window(1).unwrap().fill(1);
280 }
281
282 #[test]
283 fn partitioned_sec_buffer_2_proper() {
284 citadel_logging::setup_log();
285 let mut buf = PartitionedSecBuffer::<2>::new().unwrap();
286 buf.reserve_partition(0, 10).unwrap();
287 buf.reserve_partition(1, 3).unwrap();
288 buf.partition_window(0).unwrap().fill(1);
289 buf.partition_window(1).unwrap().fill(2);
290 assert_eq!(
291 buf.into_buffer(),
292 &vec![1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2]
293 )
294 }
295
296 #[test]
297 fn partitioned_sec_buffer_2_proper_2() {
298 citadel_logging::setup_log();
299 let mut buf = PartitionedSecBuffer::<2>::new().unwrap();
300 buf.reserve_partition(0, 10).unwrap();
301 buf.reserve_partition(1, 3).unwrap();
302 buf.partition_window(1).unwrap().fill(2); // order doesn't matter so long as reserves are set properly
303 buf.partition_window(0).unwrap().fill(1);
304 assert_eq!(
305 buf.into_buffer(),
306 &vec![1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2]
307 )
308 }
309
310 #[test]
311 #[should_panic]
312 fn partitioned_sec_buffer_2_improper() {
313 citadel_logging::should_panic_test();
314 let mut buf = PartitionedSecBuffer::<2>::new().unwrap();
315 //buf.reserve_partition(0, 10).unwrap();
316 buf.reserve_partition(1, 3).unwrap();
317 }
318
319 #[test]
320 fn partitioned_sec_buffer_3_proper() {
321 citadel_logging::setup_log();
322 let mut buf = PartitionedSecBuffer::<3>::new().unwrap();
323 buf.reserve_partition(0, 10).unwrap();
324 buf.reserve_partition(1, 3).unwrap();
325 buf.reserve_partition(2, 5).unwrap();
326 buf.partition_window(0).unwrap().fill(1);
327 buf.partition_window(1).unwrap().fill(2);
328 buf.partition_window(2).unwrap().fill(3);
329 assert_eq!(
330 buf.into_buffer(),
331 &vec![1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3]
332 )
333 }
334}