rapace_core/
validation.rs

1//! Descriptor validation.
2
3use crate::{
4    DescriptorLimits, INLINE_PAYLOAD_SIZE, INLINE_PAYLOAD_SLOT, MsgDescHot, ValidationError,
5};
6
7/// Validate a descriptor against the given limits.
8///
9/// This performs transport-agnostic validation:
10/// - Bounds checking for slots (if slot_count > 0)
11/// - Payload length limits
12/// - Inline payload constraints
13///
14/// Transport-specific validation (e.g., generation checks) happens in the transport.
15pub fn validate_descriptor(
16    desc: &MsgDescHot,
17    limits: &DescriptorLimits,
18) -> Result<(), ValidationError> {
19    // Check payload length limit
20    if desc.payload_len > limits.max_payload_len {
21        return Err(ValidationError::PayloadTooLarge {
22            len: desc.payload_len,
23            max: limits.max_payload_len,
24        });
25    }
26
27    // Check channel bounds
28    if desc.channel_id > limits.max_channels {
29        return Err(ValidationError::ChannelOutOfBounds {
30            channel: desc.channel_id,
31            max: limits.max_channels,
32        });
33    }
34
35    if desc.payload_slot == INLINE_PAYLOAD_SLOT {
36        // Inline payload validation
37        if desc.payload_len > INLINE_PAYLOAD_SIZE as u32 {
38            return Err(ValidationError::InlinePayloadTooLarge {
39                len: desc.payload_len,
40                max: INLINE_PAYLOAD_SIZE as u32,
41            });
42        }
43        // Inline descriptors must have zero slot-related fields
44        if desc.payload_generation != 0 || desc.payload_offset != 0 {
45            return Err(ValidationError::InvalidInlineDescriptor);
46        }
47    } else if limits.slot_count > 0 {
48        // Slot-based payload validation (only if we have slots)
49        if desc.payload_slot >= limits.slot_count {
50            return Err(ValidationError::SlotOutOfBounds {
51                slot: desc.payload_slot,
52                max: limits.slot_count,
53            });
54        }
55
56        let end = desc.payload_offset.saturating_add(desc.payload_len);
57        if end > limits.slot_size {
58            return Err(ValidationError::PayloadOutOfBounds {
59                offset: desc.payload_offset,
60                len: desc.payload_len,
61                slot_size: limits.slot_size,
62            });
63        }
64    }
65
66    Ok(())
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    fn make_desc() -> MsgDescHot {
74        MsgDescHot::new()
75    }
76
77    #[test]
78    fn test_valid_inline_payload() {
79        let mut desc = make_desc();
80        desc.payload_len = 10;
81        desc.inline_payload[..10].fill(0xAB);
82
83        let limits = DescriptorLimits::default();
84        assert!(validate_descriptor(&desc, &limits).is_ok());
85    }
86
87    #[test]
88    fn test_inline_payload_too_large() {
89        let mut desc = make_desc();
90        desc.payload_len = 30; // > INLINE_PAYLOAD_SIZE (24)
91
92        let limits = DescriptorLimits::default();
93        let err = validate_descriptor(&desc, &limits).unwrap_err();
94        assert!(matches!(err, ValidationError::InlinePayloadTooLarge { .. }));
95    }
96
97    #[test]
98    fn test_invalid_inline_descriptor() {
99        let mut desc = make_desc();
100        desc.payload_len = 10;
101        desc.payload_generation = 1; // Should be 0 for inline
102
103        let limits = DescriptorLimits::default();
104        let err = validate_descriptor(&desc, &limits).unwrap_err();
105        assert!(matches!(err, ValidationError::InvalidInlineDescriptor));
106    }
107
108    #[test]
109    fn test_payload_too_large() {
110        let mut desc = make_desc();
111        desc.payload_slot = 0;
112        desc.payload_len = 2 * 1024 * 1024; // 2MB > default 1MB limit
113
114        let limits = DescriptorLimits::with_slots(16, 4096);
115        let err = validate_descriptor(&desc, &limits).unwrap_err();
116        assert!(matches!(err, ValidationError::PayloadTooLarge { .. }));
117    }
118
119    #[test]
120    fn test_slot_out_of_bounds() {
121        let mut desc = make_desc();
122        desc.payload_slot = 100;
123        desc.payload_len = 100;
124
125        let limits = DescriptorLimits::with_slots(16, 4096);
126        let err = validate_descriptor(&desc, &limits).unwrap_err();
127        assert!(matches!(err, ValidationError::SlotOutOfBounds { .. }));
128    }
129
130    #[test]
131    fn test_payload_out_of_bounds() {
132        let mut desc = make_desc();
133        desc.payload_slot = 0;
134        desc.payload_offset = 4000;
135        desc.payload_len = 200; // 4000 + 200 = 4200 > 4096
136
137        let limits = DescriptorLimits::with_slots(16, 4096);
138        let err = validate_descriptor(&desc, &limits).unwrap_err();
139        assert!(matches!(err, ValidationError::PayloadOutOfBounds { .. }));
140    }
141
142    #[test]
143    fn test_channel_out_of_bounds() {
144        let mut desc = make_desc();
145        desc.channel_id = 2000;
146
147        let limits = DescriptorLimits::default();
148        let err = validate_descriptor(&desc, &limits).unwrap_err();
149        assert!(matches!(err, ValidationError::ChannelOutOfBounds { .. }));
150    }
151
152    #[test]
153    fn test_valid_slot_payload() {
154        let mut desc = make_desc();
155        desc.payload_slot = 5;
156        desc.payload_generation = 42;
157        desc.payload_offset = 100;
158        desc.payload_len = 500;
159
160        let limits = DescriptorLimits::with_slots(16, 4096);
161        assert!(validate_descriptor(&desc, &limits).is_ok());
162    }
163}