trace2e_core/traceability/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::{P2mRequest, P2mResponse},
93 core::{
94 compliance::ComplianceService, provenance::ProvenanceService,
95 sequencer::SequencerService,
96 },
97 p2m::P2mApiService,
98 },
99 transport::nop::M2mNop,
100 };
101
102 #[tokio::test]
103 async fn unit_traceability_provenance_service_p2m_validator() {
104 let mut p2m_service = P2mApiService::new(
105 SequencerService::default(),
106 ProvenanceService::default(),
107 ComplianceService::default(),
108 M2mNop,
109 )
110 .with_resource_validation(true);
111
112 assert_eq!(
113 p2m_service
114 .call(P2mRequest::LocalEnroll { pid: 1, fd: 1, path: "test".to_string() })
115 .await
116 .unwrap(),
117 P2mResponse::Ack
118 );
119
120 assert_eq!(
121 p2m_service
122 .call(P2mRequest::RemoteEnroll {
123 pid: 1,
124 local_socket: "127.0.0.1:8080".to_string(),
125 peer_socket: "127.0.0.1:8081".to_string(),
126 fd: 1
127 })
128 .await
129 .unwrap(),
130 P2mResponse::Ack
131 );
132
133 let P2mResponse::Grant(flow_id) =
134 p2m_service.call(P2mRequest::IoRequest { pid: 1, fd: 1, output: true }).await.unwrap()
135 else {
136 panic!("Expected P2mResponse::Grant");
137 };
138 assert_eq!(
139 p2m_service
140 .call(P2mRequest::IoReport { pid: 1, fd: 1, grant_id: flow_id, result: true })
141 .await
142 .unwrap(),
143 P2mResponse::Ack
144 );
145
146 assert_eq!(
147 p2m_service
148 .call(P2mRequest::LocalEnroll { pid: 0, fd: 1, path: "test".to_string() }) // pid 0 is invalid
149 .await
150 .unwrap_err()
151 .to_string(),
152 "Traceability error, process not found (pid: 0)"
153 );
154
155 assert_eq!(
156 p2m_service
157 .call(P2mRequest::RemoteEnroll {
158 pid: 1,
159 local_socket: "bad_socket".to_string(),
160 peer_socket: "bad_socket".to_string(),
161 fd: 1
162 })
163 .await
164 .unwrap_err()
165 .to_string(),
166 "Traceability error, invalid stream (local_socket: bad_socket, peer_socket: bad_socket)"
167 );
168 }
169}