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}