trace2e_core/traceability/infrastructure/validation.rs
1//! Request validation and filtering for traceability operations.
2//!
3//! This module provides validation mechanisms to ensure that incoming requests
4//! contain valid, accessible resources before they are processed by the core
5//! traceability services. It acts as a gatekeeper to prevent invalid operations
6//! from reaching the business logic layer.
7//!
8//! ## Validation Categories
9//!
10//! **Process Validation**: Verifies that process IDs refer to actual running processes
11//! by querying the system process table.
12//!
13//! **Stream Validation**: Ensures that socket addresses are well-formed and compatible
14//! (e.g., both IPv4 or both IPv6) for network stream operations.
15//!
16//! ## Integration
17//!
18//! The validator is designed to work with the P2M service through the embedded validation
19//! feature. Invalid requests are rejected early with descriptive error messages before
20//! consuming system resources.
21
22use std::net::SocketAddr;
23
24use sysinfo::{Pid, System};
25
26/// Resource validator for P2M requests.
27///
28/// This validator ensures that incoming Process-to-Middleware requests reference
29/// valid, accessible system resources before they are processed by the core services.
30/// It performs system-level validation that cannot be done at the type level.
31///
32/// ## Validation Rules
33///
34/// - **Process IDs**: Must correspond to currently running processes
35/// - **Socket Addresses**: Must be parseable and use compatible address families
36/// - **File Descriptors**: Accepted without validation (OS handles validity)
37///
38/// ## Usage
39///
40/// The validator methods are called by the P2M service when resource validation
41/// is enabled through the `with_resource_validation(true)` builder method.
42#[derive(Debug, Clone)]
43pub struct ResourceValidator;
44
45impl ResourceValidator {
46 /// Validates that a process ID corresponds to a currently running process.
47 ///
48 /// Queries the system process table to verify that the specified PID
49 /// is associated with an active process. This prevents operations on
50 /// stale or invalid process identifiers.
51 ///
52 /// # Arguments
53 /// * `pid` - Process identifier to validate
54 ///
55 /// # Returns
56 /// `true` if the process exists and is accessible, `false` otherwise
57 pub fn is_valid_process(&self, pid: i32) -> bool {
58 let mut system = System::new();
59 system.refresh_all();
60 system.process(Pid::from(pid as usize)).is_some()
61 }
62
63 /// Validates that socket addresses are well-formed and compatible.
64 ///
65 /// Ensures that both local and peer socket addresses can be parsed
66 /// and use compatible address families (both IPv4 or both IPv6).
67 /// This prevents network operations with mismatched or invalid addresses.
68 ///
69 /// # Arguments
70 /// * `local_socket` - Local socket address string
71 /// * `peer_socket` - Peer socket address string
72 ///
73 /// # Returns
74 /// `true` if both addresses are valid and compatible, `false` otherwise
75 pub fn is_valid_stream(&self, local_socket: &str, peer_socket: &str) -> bool {
76 match (local_socket.parse::<SocketAddr>(), peer_socket.parse::<SocketAddr>()) {
77 (Ok(local_socket), Ok(peer_socket)) => {
78 (local_socket.is_ipv4() && peer_socket.is_ipv4())
79 || (local_socket.is_ipv6() && peer_socket.is_ipv6())
80 }
81 _ => false,
82 }
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use tower::Service;
89
90 use crate::{
91 traceability::{
92 api::{
93 p2m::P2mApiService,
94 types::{P2mRequest, P2mResponse},
95 },
96 services::{
97 compliance::ComplianceService, provenance::ProvenanceService,
98 sequencer::SequencerService,
99 },
100 },
101 transport::nop::M2mNop,
102 };
103
104 #[tokio::test]
105 async fn unit_traceability_provenance_service_p2m_validator() {
106 let mut p2m_service = P2mApiService::new(
107 SequencerService::default(),
108 ProvenanceService::default(),
109 ComplianceService::default(),
110 M2mNop,
111 )
112 .with_resource_validation(true);
113
114 assert_eq!(
115 p2m_service
116 .call(P2mRequest::LocalEnroll { pid: 1, fd: 1, path: "test".to_string() })
117 .await
118 .unwrap(),
119 P2mResponse::Ack
120 );
121
122 assert_eq!(
123 p2m_service
124 .call(P2mRequest::RemoteEnroll {
125 pid: 1,
126 local_socket: "127.0.0.1:8080".to_string(),
127 peer_socket: "127.0.0.1:8081".to_string(),
128 fd: 1
129 })
130 .await
131 .unwrap(),
132 P2mResponse::Ack
133 );
134
135 let P2mResponse::Grant(flow_id) =
136 p2m_service.call(P2mRequest::IoRequest { pid: 1, fd: 1, output: true }).await.unwrap()
137 else {
138 panic!("Expected P2mResponse::Grant");
139 };
140 assert_eq!(
141 p2m_service
142 .call(P2mRequest::IoReport { pid: 1, fd: 1, grant_id: flow_id, result: true })
143 .await
144 .unwrap(),
145 P2mResponse::Ack
146 );
147
148 assert_eq!(
149 p2m_service
150 .call(P2mRequest::LocalEnroll { pid: 0, fd: 1, path: "test".to_string() }) // pid 0 is invalid
151 .await
152 .unwrap_err()
153 .to_string(),
154 "Traceability error, process not found (pid: 0)"
155 );
156
157 assert_eq!(
158 p2m_service
159 .call(P2mRequest::RemoteEnroll {
160 pid: 1,
161 local_socket: "bad_socket".to_string(),
162 peer_socket: "bad_socket".to_string(),
163 fd: 1
164 })
165 .await
166 .unwrap_err()
167 .to_string(),
168 "Traceability error, invalid stream (local_socket: bad_socket, peer_socket: bad_socket)"
169 );
170 }
171}