Skip to main content

ruvix_queue/
descriptor.rs

1//! Zero-copy message descriptors.
2//!
3//! When sender and receiver share a region, messages can be passed by reference
4//! using descriptors instead of copying data. This module provides the descriptor
5//! type and validation logic.
6
7use ruvix_types::{KernelError, MsgPriority, RegionHandle, RegionPolicy};
8
9use crate::Result;
10
11/// A zero-copy message descriptor.
12///
13/// Instead of copying message data into the ring buffer, a descriptor
14/// references data in a shared region. The receiver reads directly from
15/// the shared region using the offset and length.
16///
17/// # TOCTOU Protection
18///
19/// Only Immutable or AppendOnly regions can use descriptors (ADR-087 Section 20.5).
20/// This prevents time-of-check-to-time-of-use attacks where a sender modifies
21/// shared data after the receiver reads the descriptor but before processing.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23#[repr(C)]
24pub struct MessageDescriptor {
25    /// Handle to the shared region containing the data.
26    pub region: RegionHandle,
27    /// Byte offset within the region.
28    pub offset: u64,
29    /// Length in bytes.
30    pub length: u32,
31    /// Reserved padding (ensures 8-byte alignment).
32    _padding: u32,
33}
34
35impl MessageDescriptor {
36    /// Size of a descriptor in bytes.
37    pub const SIZE: usize = core::mem::size_of::<Self>();
38
39    /// Create a new message descriptor.
40    #[inline]
41    pub const fn new(region: RegionHandle, offset: u64, length: u32) -> Self {
42        Self {
43            region,
44            offset,
45            length,
46            _padding: 0,
47        }
48    }
49
50    /// Check if this descriptor is valid (non-null region, non-zero length).
51    #[inline]
52    pub fn is_valid(&self) -> bool {
53        !self.region.is_null() && self.length > 0
54    }
55
56    /// Convert to bytes for storage in ring buffer.
57    #[inline]
58    pub fn to_bytes(&self) -> [u8; Self::SIZE] {
59        // SAFETY: MessageDescriptor is repr(C) with no padding
60        unsafe { core::mem::transmute(*self) }
61    }
62
63    /// Create from bytes read from ring buffer.
64    #[inline]
65    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
66        if bytes.len() < Self::SIZE {
67            return None;
68        }
69
70        let mut arr = [0u8; Self::SIZE];
71        arr.copy_from_slice(&bytes[..Self::SIZE]);
72
73        // SAFETY: MessageDescriptor is repr(C) with defined layout
74        Some(unsafe { core::mem::transmute(arr) })
75    }
76}
77
78/// Validator for message descriptors.
79///
80/// Ensures that descriptors only reference regions with policies that
81/// are safe for zero-copy message passing (Immutable or AppendOnly).
82pub struct DescriptorValidator {
83    /// Whether to allow Immutable regions.
84    allow_immutable: bool,
85    /// Whether to allow AppendOnly regions.
86    allow_append_only: bool,
87}
88
89impl DescriptorValidator {
90    /// Create a new validator with default settings (Immutable and AppendOnly allowed).
91    pub const fn new() -> Self {
92        Self {
93            allow_immutable: true,
94            allow_append_only: true,
95        }
96    }
97
98    /// Create a validator that only allows Immutable regions.
99    pub const fn immutable_only() -> Self {
100        Self {
101            allow_immutable: true,
102            allow_append_only: false,
103        }
104    }
105
106    /// Validate that a region policy is safe for descriptor-based messaging.
107    ///
108    /// # Errors
109    ///
110    /// Returns `InvalidDescriptorRegion` if the policy doesn't allow descriptors.
111    pub fn validate_policy(&self, policy: &RegionPolicy) -> Result<()> {
112        match policy {
113            RegionPolicy::Immutable if self.allow_immutable => Ok(()),
114            RegionPolicy::AppendOnly { .. } if self.allow_append_only => Ok(()),
115            RegionPolicy::Slab { .. } => {
116                // Slab regions allow overwriting, which creates TOCTOU vulnerabilities
117                Err(KernelError::InvalidDescriptorRegion)
118            }
119            _ => Err(KernelError::InvalidDescriptorRegion),
120        }
121    }
122
123    /// Validate a descriptor against region bounds.
124    ///
125    /// # Arguments
126    ///
127    /// * `descriptor` - The descriptor to validate
128    /// * `region_size` - The total size of the region
129    ///
130    /// # Errors
131    ///
132    /// Returns `InvalidParameter` if the descriptor references memory outside the region.
133    pub fn validate_bounds(&self, descriptor: &MessageDescriptor, region_size: usize) -> Result<()> {
134        let end = descriptor
135            .offset
136            .checked_add(descriptor.length as u64)
137            .ok_or(KernelError::InvalidArgument)?;
138
139        if end > region_size as u64 {
140            return Err(KernelError::InvalidArgument);
141        }
142
143        Ok(())
144    }
145
146    /// Full validation of a descriptor.
147    ///
148    /// Checks that:
149    /// 1. The descriptor is valid (non-null region, non-zero length)
150    /// 2. The region policy allows descriptors
151    /// 3. The offset + length is within region bounds
152    pub fn validate(
153        &self,
154        descriptor: &MessageDescriptor,
155        policy: &RegionPolicy,
156        region_size: usize,
157    ) -> Result<()> {
158        if !descriptor.is_valid() {
159            return Err(KernelError::InvalidArgument);
160        }
161
162        self.validate_policy(policy)?;
163        self.validate_bounds(descriptor, region_size)?;
164
165        Ok(())
166    }
167}
168
169impl Default for DescriptorValidator {
170    fn default() -> Self {
171        Self::new()
172    }
173}
174
175/// A descriptor with attached priority for queue operations.
176#[derive(Debug, Clone, Copy)]
177pub struct PrioritizedDescriptor {
178    /// The underlying descriptor.
179    pub descriptor: MessageDescriptor,
180    /// Message priority.
181    pub priority: MsgPriority,
182}
183
184impl PrioritizedDescriptor {
185    /// Create a new prioritized descriptor.
186    #[inline]
187    pub const fn new(descriptor: MessageDescriptor, priority: MsgPriority) -> Self {
188        Self {
189            descriptor,
190            priority,
191        }
192    }
193
194    /// Create with default (normal) priority.
195    #[inline]
196    pub const fn with_normal_priority(descriptor: MessageDescriptor) -> Self {
197        Self {
198            descriptor,
199            priority: MsgPriority::Normal,
200        }
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207    use ruvix_types::Handle;
208
209    fn test_region() -> RegionHandle {
210        RegionHandle(Handle::new(1, 0))
211    }
212
213    #[test]
214    fn test_descriptor_size() {
215        // Ensure predictable size for ring buffer calculations
216        assert_eq!(MessageDescriptor::SIZE, 24);
217    }
218
219    #[test]
220    fn test_descriptor_roundtrip() {
221        let desc = MessageDescriptor::new(test_region(), 100, 256);
222        let bytes = desc.to_bytes();
223        let recovered = MessageDescriptor::from_bytes(&bytes).unwrap();
224
225        assert_eq!(desc.region, recovered.region);
226        assert_eq!(desc.offset, recovered.offset);
227        assert_eq!(desc.length, recovered.length);
228    }
229
230    #[test]
231    fn test_descriptor_validation_null() {
232        let desc = MessageDescriptor::new(RegionHandle::null(), 0, 100);
233        assert!(!desc.is_valid());
234    }
235
236    #[test]
237    fn test_descriptor_validation_zero_length() {
238        let desc = MessageDescriptor::new(test_region(), 0, 0);
239        assert!(!desc.is_valid());
240    }
241
242    #[test]
243    fn test_validator_immutable_ok() {
244        let validator = DescriptorValidator::new();
245        let result = validator.validate_policy(&RegionPolicy::Immutable);
246        assert!(result.is_ok());
247    }
248
249    #[test]
250    fn test_validator_append_only_ok() {
251        let validator = DescriptorValidator::new();
252        let result = validator.validate_policy(&RegionPolicy::AppendOnly { max_size: 1024 });
253        assert!(result.is_ok());
254    }
255
256    #[test]
257    fn test_validator_slab_rejected() {
258        let validator = DescriptorValidator::new();
259        let result = validator.validate_policy(&RegionPolicy::Slab {
260            slot_size: 64,
261            slot_count: 16,
262        });
263        assert!(matches!(result, Err(KernelError::InvalidDescriptorRegion)));
264    }
265
266    #[test]
267    fn test_validator_bounds() {
268        let validator = DescriptorValidator::new();
269        let desc = MessageDescriptor::new(test_region(), 100, 256);
270
271        // Within bounds
272        assert!(validator.validate_bounds(&desc, 500).is_ok());
273
274        // Exactly at boundary
275        assert!(validator.validate_bounds(&desc, 356).is_ok());
276
277        // Out of bounds
278        assert!(validator.validate_bounds(&desc, 355).is_err());
279    }
280
281    #[test]
282    fn test_validator_bounds_overflow() {
283        let validator = DescriptorValidator::new();
284        let desc = MessageDescriptor::new(test_region(), u64::MAX - 10, 100);
285
286        // This would overflow, should be detected
287        assert!(validator.validate_bounds(&desc, 1000).is_err());
288    }
289
290    #[test]
291    fn test_full_validation() {
292        let validator = DescriptorValidator::new();
293        let desc = MessageDescriptor::new(test_region(), 100, 256);
294
295        // Valid descriptor with immutable region
296        assert!(validator
297            .validate(&desc, &RegionPolicy::Immutable, 500)
298            .is_ok());
299
300        // Invalid descriptor with slab region
301        assert!(validator
302            .validate(
303                &desc,
304                &RegionPolicy::Slab {
305                    slot_size: 64,
306                    slot_count: 16
307                },
308                500
309            )
310            .is_err());
311    }
312
313    #[test]
314    fn test_immutable_only_validator() {
315        let validator = DescriptorValidator::immutable_only();
316
317        // Immutable should be allowed
318        assert!(validator.validate_policy(&RegionPolicy::Immutable).is_ok());
319
320        // AppendOnly should be rejected
321        assert!(validator
322            .validate_policy(&RegionPolicy::AppendOnly { max_size: 1024 })
323            .is_err());
324    }
325}