Skip to main content

ferrotunnel_protocol/
validation.rs

1//! Frame validation for security hardening
2
3use crate::frame::Frame;
4
5/// Validation errors
6#[derive(Debug, thiserror::Error)]
7pub enum ValidationError {
8    #[error("Frame too large: {size} bytes exceeds limit of {limit} bytes")]
9    FrameTooLarge { size: u64, limit: u64 },
10
11    #[error("Token too long: {len} bytes exceeds limit of {limit} bytes")]
12    TokenTooLong { len: usize, limit: usize },
13
14    #[error("Too many capabilities: {count} exceeds limit of {limit}")]
15    TooManyCapabilities { count: usize, limit: usize },
16
17    #[error("Capability too long: {len} bytes exceeds limit of {limit} bytes")]
18    CapabilityTooLong { len: usize, limit: usize },
19
20    #[error("Payload too large: {size} bytes exceeds limit of {limit} bytes")]
21    PayloadTooLarge { size: usize, limit: usize },
22}
23
24/// Validation limits
25#[derive(Debug, Clone)]
26pub struct ValidationLimits {
27    pub max_frame_bytes: u64,
28    pub max_token_len: usize,
29    pub max_capabilities: usize,
30    pub max_capability_len: usize,
31    pub max_payload_bytes: usize,
32}
33
34impl Default for ValidationLimits {
35    fn default() -> Self {
36        Self {
37            max_frame_bytes: 16 * 1024 * 1024,
38            max_token_len: 256,
39            max_capabilities: 32,
40            max_capability_len: 64,
41            max_payload_bytes: 16 * 1024 * 1024,
42        }
43    }
44}
45
46/// Validate a decoded frame against limits
47pub fn validate_frame(frame: &Frame, limits: &ValidationLimits) -> Result<(), ValidationError> {
48    match frame {
49        Frame::Handshake(handshake) => {
50            if handshake.token.len() > limits.max_token_len {
51                return Err(ValidationError::TokenTooLong {
52                    len: handshake.token.len(),
53                    limit: limits.max_token_len,
54                });
55            }
56            if handshake.capabilities.len() > limits.max_capabilities {
57                return Err(ValidationError::TooManyCapabilities {
58                    count: handshake.capabilities.len(),
59                    limit: limits.max_capabilities,
60                });
61            }
62            for cap in &handshake.capabilities {
63                if cap.len() > limits.max_capability_len {
64                    return Err(ValidationError::CapabilityTooLong {
65                        len: cap.len(),
66                        limit: limits.max_capability_len,
67                    });
68                }
69            }
70        }
71        Frame::HandshakeAck {
72            server_capabilities,
73            ..
74        } => {
75            if server_capabilities.len() > limits.max_capabilities {
76                return Err(ValidationError::TooManyCapabilities {
77                    count: server_capabilities.len(),
78                    limit: limits.max_capabilities,
79                });
80            }
81            for cap in server_capabilities {
82                if cap.len() > limits.max_capability_len {
83                    return Err(ValidationError::CapabilityTooLong {
84                        len: cap.len(),
85                        limit: limits.max_capability_len,
86                    });
87                }
88            }
89        }
90        Frame::Data { data, .. } => {
91            if data.len() > limits.max_payload_bytes {
92                return Err(ValidationError::PayloadTooLarge {
93                    size: data.len(),
94                    limit: limits.max_payload_bytes,
95                });
96            }
97        }
98        _ => {}
99    }
100    Ok(())
101}