Skip to main content

phantom_protocol/
validation.rs

1//! Standalone input-validation helpers (DoS / malformed-input guards).
2//!
3//! [`InputValidator`] is a stateless bag of bounds checks — message size, a
4//! 16-byte group-id well-formedness check, and an epoch-drift window. They are
5//! generic guards, NOT bound to the live `PacketHeader` codec: the helpers here
6//! are not invoked on the production receive path (the wire `epoch` is a `u8`
7//! rekey selector validated inside `transport::session`, unrelated to the `u64`
8//! application-level epoch checked here). The module is kept as a reusable
9//! validation surface for embedders and for any caller that needs to bound
10//! untrusted input before handing it to the core.
11
12use anyhow::{bail, Result};
13
14pub struct InputValidator;
15
16impl InputValidator {
17    /// Validate message size to prevent DoS (OOM)
18    pub fn validate_message_size(data: &[u8], max_size: usize) -> Result<()> {
19        if data.is_empty() {
20            bail!("Empty message");
21        }
22        if data.len() > max_size {
23            bail!(
24                "Message too large: {} bytes (max: {})",
25                data.len(),
26                max_size
27            );
28        }
29        Ok(())
30    }
31
32    /// Validate Group ID length (must be 16 bytes)
33    pub fn validate_group_id(group_id: &[u8]) -> Result<[u8; 16]> {
34        if group_id.len() != 16 {
35            bail!(
36                "Invalid group_id length: expected 16, got {}",
37                group_id.len()
38            );
39        }
40
41        // Check for zero-ID (reserved)
42        if group_id.iter().all(|&b| b == 0) {
43            bail!("Zero group_id not allowed");
44        }
45
46        let mut gid = [0u8; 16];
47        gid.copy_from_slice(group_id);
48        Ok(gid)
49    }
50
51    /// Validate Epoch to prevent processing very old or future frames
52    pub fn validate_epoch(epoch: u64, current_epoch: u64) -> Result<()> {
53        const MAX_EPOCH_DRIFT: u64 = 50; // Allow some drift for commit implementation
54
55        // Future check
56        if epoch > current_epoch + MAX_EPOCH_DRIFT {
57            bail!(
58                "Epoch too far in future: {} (current: {})",
59                epoch,
60                current_epoch
61            );
62        }
63
64        // Past check (strictness depends on application logic, usually we reject old epochs)
65        // Here we allow some history for reordering
66        if current_epoch > MAX_EPOCH_DRIFT && epoch < current_epoch - MAX_EPOCH_DRIFT {
67            bail!("Epoch too old: {} (current: {})", epoch, current_epoch);
68        }
69
70        Ok(())
71    }
72}