use crate::frame::{ArchivedProxyFrame, ProxyFrame};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ValidationError {
#[error("Duplicate frame UUID detected")]
DuplicateUuid,
#[error("Checksum mismatch: expected {expected}, got {actual}")]
ChecksumMismatch { expected: u32, actual: u32 },
#[error("Timestamp out of range: {0}ms drift")]
TimestampOutOfRange(i64),
#[error("Invalid connection ID")]
InvalidConnId,
#[error("Payload too large: {size} bytes (max: {max})")]
PayloadTooLarge { size: usize, max: usize },
}
pub const MAX_PAYLOAD_SIZE: usize = 65536;
pub const MAX_TIMESTAMP_DRIFT_MS: i64 = 30_000;
pub fn validate_frame(frame: &ProxyFrame, current_time_ms: u64) -> Result<(), ValidationError> {
if frame.payload.len() > MAX_PAYLOAD_SIZE {
return Err(ValidationError::PayloadTooLarge {
size: frame.payload.len(),
max: MAX_PAYLOAD_SIZE,
});
}
let computed = crc32fast::hash(&frame.payload);
if computed != frame.checksum {
return Err(ValidationError::ChecksumMismatch {
expected: frame.checksum,
actual: computed,
});
}
let drift = current_time_ms as i64 - frame.timestamp as i64;
if drift.abs() > MAX_TIMESTAMP_DRIFT_MS {
return Err(ValidationError::TimestampOutOfRange(drift));
}
Ok(())
}
pub fn validate_archived_frame(
frame: &ArchivedProxyFrame,
current_time_ms: u64,
) -> Result<(), ValidationError> {
if frame.payload.len() > MAX_PAYLOAD_SIZE {
return Err(ValidationError::PayloadTooLarge {
size: frame.payload.len(),
max: MAX_PAYLOAD_SIZE,
});
}
let frame_checksum: u32 = frame.checksum.to_native();
let computed = crc32fast::hash(&frame.payload);
if computed != frame_checksum {
return Err(ValidationError::ChecksumMismatch {
expected: frame_checksum,
actual: computed,
});
}
let frame_timestamp: u64 = frame.timestamp.to_native();
let drift = current_time_ms as i64 - frame_timestamp as i64;
if drift.abs() > MAX_TIMESTAMP_DRIFT_MS {
return Err(ValidationError::TimestampOutOfRange(drift));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_frame() {
let frame = ProxyFrame::new_data(1, [0; 16], 443, vec![1, 2, 3]);
let result = validate_frame(&frame, frame.timestamp);
assert!(result.is_ok());
}
#[test]
fn test_checksum_mismatch() {
let mut frame = ProxyFrame::new_data(1, [0; 16], 443, vec![1, 2, 3]);
frame.checksum = 0xDEADBEEF;
let result = validate_frame(&frame, frame.timestamp);
assert!(matches!(
result,
Err(ValidationError::ChecksumMismatch { .. })
));
}
#[test]
fn test_timestamp_drift() {
let frame = ProxyFrame::new_data(1, [0; 16], 443, vec![1, 2, 3]);
let future_time = frame.timestamp + 60_000;
let result = validate_frame(&frame, future_time);
assert!(matches!(
result,
Err(ValidationError::TimestampOutOfRange(_))
));
}
#[test]
fn test_payload_too_large() {
let large_payload = vec![0u8; MAX_PAYLOAD_SIZE + 1];
let frame = ProxyFrame::new_data(1, [0; 16], 443, large_payload);
let result = validate_frame(&frame, frame.timestamp);
assert!(matches!(
result,
Err(ValidationError::PayloadTooLarge { .. })
));
}
}