apfsds_protocol/
validation.rs1use crate::frame::{ArchivedProxyFrame, ProxyFrame};
4use thiserror::Error;
5
6#[derive(Error, Debug)]
8pub enum ValidationError {
9 #[error("Duplicate frame UUID detected")]
10 DuplicateUuid,
11
12 #[error("Checksum mismatch: expected {expected}, got {actual}")]
13 ChecksumMismatch { expected: u32, actual: u32 },
14
15 #[error("Timestamp out of range: {0}ms drift")]
16 TimestampOutOfRange(i64),
17
18 #[error("Invalid connection ID")]
19 InvalidConnId,
20
21 #[error("Payload too large: {size} bytes (max: {max})")]
22 PayloadTooLarge { size: usize, max: usize },
23}
24
25pub const MAX_PAYLOAD_SIZE: usize = 65536;
27
28pub const MAX_TIMESTAMP_DRIFT_MS: i64 = 30_000;
30
31pub fn validate_frame(frame: &ProxyFrame, current_time_ms: u64) -> Result<(), ValidationError> {
33 if frame.payload.len() > MAX_PAYLOAD_SIZE {
35 return Err(ValidationError::PayloadTooLarge {
36 size: frame.payload.len(),
37 max: MAX_PAYLOAD_SIZE,
38 });
39 }
40
41 let computed = crc32fast::hash(&frame.payload);
43 if computed != frame.checksum {
44 return Err(ValidationError::ChecksumMismatch {
45 expected: frame.checksum,
46 actual: computed,
47 });
48 }
49
50 let drift = current_time_ms as i64 - frame.timestamp as i64;
52 if drift.abs() > MAX_TIMESTAMP_DRIFT_MS {
53 return Err(ValidationError::TimestampOutOfRange(drift));
54 }
55
56 Ok(())
57}
58
59pub fn validate_archived_frame(
61 frame: &ArchivedProxyFrame,
62 current_time_ms: u64,
63) -> Result<(), ValidationError> {
64 if frame.payload.len() > MAX_PAYLOAD_SIZE {
66 return Err(ValidationError::PayloadTooLarge {
67 size: frame.payload.len(),
68 max: MAX_PAYLOAD_SIZE,
69 });
70 }
71
72 let frame_checksum: u32 = frame.checksum.to_native();
74 let computed = crc32fast::hash(&frame.payload);
75 if computed != frame_checksum {
76 return Err(ValidationError::ChecksumMismatch {
77 expected: frame_checksum,
78 actual: computed,
79 });
80 }
81
82 let frame_timestamp: u64 = frame.timestamp.to_native();
84 let drift = current_time_ms as i64 - frame_timestamp as i64;
85 if drift.abs() > MAX_TIMESTAMP_DRIFT_MS {
86 return Err(ValidationError::TimestampOutOfRange(drift));
87 }
88
89 Ok(())
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn test_valid_frame() {
98 let frame = ProxyFrame::new_data(1, [0; 16], 443, vec![1, 2, 3]);
99 let result = validate_frame(&frame, frame.timestamp);
100 assert!(result.is_ok());
101 }
102
103 #[test]
104 fn test_checksum_mismatch() {
105 let mut frame = ProxyFrame::new_data(1, [0; 16], 443, vec![1, 2, 3]);
106 frame.checksum = 0xDEADBEEF; let result = validate_frame(&frame, frame.timestamp);
109 assert!(matches!(
110 result,
111 Err(ValidationError::ChecksumMismatch { .. })
112 ));
113 }
114
115 #[test]
116 fn test_timestamp_drift() {
117 let frame = ProxyFrame::new_data(1, [0; 16], 443, vec![1, 2, 3]);
118 let future_time = frame.timestamp + 60_000; let result = validate_frame(&frame, future_time);
121 assert!(matches!(
122 result,
123 Err(ValidationError::TimestampOutOfRange(_))
124 ));
125 }
126
127 #[test]
128 fn test_payload_too_large() {
129 let large_payload = vec![0u8; MAX_PAYLOAD_SIZE + 1];
130 let frame = ProxyFrame::new_data(1, [0; 16], 443, large_payload);
131
132 let result = validate_frame(&frame, frame.timestamp);
133 assert!(matches!(
134 result,
135 Err(ValidationError::PayloadTooLarge { .. })
136 ));
137 }
138}