tap_node/validation/
timestamp_validator.rs1use super::{MessageValidator, ValidationResult};
4use async_trait::async_trait;
5use chrono::{DateTime, Duration, Utc};
6use tap_msg::didcomm::PlainMessage;
7
8pub struct TimestampValidator {
15 max_future_drift_secs: i64,
16}
17
18impl TimestampValidator {
19 pub fn new(max_future_drift_secs: i64) -> Self {
24 Self {
25 max_future_drift_secs,
26 }
27 }
28
29 fn timestamp_to_datetime(timestamp: u64) -> DateTime<Utc> {
31 if timestamp > i64::MAX as u64 {
33 DateTime::parse_from_rfc3339("3000-01-01T00:00:00Z")
35 .unwrap()
36 .with_timezone(&Utc)
37 } else {
38 DateTime::from_timestamp(timestamp as i64, 0).unwrap_or_else(Utc::now)
39 }
40 }
41}
42
43#[async_trait]
44impl MessageValidator for TimestampValidator {
45 async fn validate(&self, message: &PlainMessage) -> ValidationResult {
46 let now = Utc::now();
47
48 if let Some(created_time) = message.created_time {
50 let created_dt = Self::timestamp_to_datetime(created_time);
51
52 let max_future = now + Duration::seconds(self.max_future_drift_secs);
54 if created_dt > max_future {
55 return ValidationResult::Reject(format!(
56 "Message created_time is too far in the future: {} (max allowed: {})",
57 created_dt, max_future
58 ));
59 }
60 }
61
62 if let Some(expires_time) = message.expires_time {
64 let expires_dt = Self::timestamp_to_datetime(expires_time);
65
66 if now > expires_dt {
68 return ValidationResult::Reject(format!(
69 "Message has expired at: {} (current time: {})",
70 expires_dt, now
71 ));
72 }
73 }
74
75 ValidationResult::Accept
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82 use chrono::Duration;
83
84 #[tokio::test]
85 async fn test_valid_timestamp() {
86 let validator = TimestampValidator::new(60);
87 let message = PlainMessage::new(
88 "test_msg_1".to_string(),
89 "test_type".to_string(),
90 serde_json::json!({}),
91 "did:example:sender".to_string(),
92 )
93 .with_recipient("did:example:receiver");
94
95 match validator.validate(&message).await {
98 ValidationResult::Accept => {} ValidationResult::Reject(reason) => panic!("Expected accept, got reject: {}", reason),
100 }
101 }
102
103 #[tokio::test]
104 async fn test_future_timestamp_within_drift() {
105 let validator = TimestampValidator::new(60);
106 let mut message = PlainMessage::new(
107 "test_msg_2".to_string(),
108 "test_type".to_string(),
109 serde_json::json!({}),
110 "did:example:sender".to_string(),
111 )
112 .with_recipient("did:example:receiver");
113
114 let future_time = Utc::now() + Duration::seconds(30);
116 message.created_time = Some(future_time.timestamp() as u64);
117
118 match validator.validate(&message).await {
119 ValidationResult::Accept => {} ValidationResult::Reject(reason) => panic!("Expected accept, got reject: {}", reason),
121 }
122 }
123
124 #[tokio::test]
125 async fn test_future_timestamp_exceeds_drift() {
126 let validator = TimestampValidator::new(60);
127 let mut message = PlainMessage::new(
128 "test_msg_2".to_string(),
129 "test_type".to_string(),
130 serde_json::json!({}),
131 "did:example:sender".to_string(),
132 )
133 .with_recipient("did:example:receiver");
134
135 let future_time = Utc::now() + Duration::seconds(120);
137 message.created_time = Some(future_time.timestamp() as u64);
138
139 match validator.validate(&message).await {
140 ValidationResult::Accept => panic!("Expected reject, got accept"),
141 ValidationResult::Reject(reason) => {
142 assert!(reason.contains("too far in the future"));
143 }
144 }
145 }
146
147 #[tokio::test]
148 async fn test_expired_message() {
149 let validator = TimestampValidator::new(60);
150 let mut message = PlainMessage::new(
151 "test_msg_2".to_string(),
152 "test_type".to_string(),
153 serde_json::json!({}),
154 "did:example:sender".to_string(),
155 )
156 .with_recipient("did:example:receiver");
157
158 let expired_time = Utc::now() - Duration::seconds(60);
160 message.expires_time = Some(expired_time.timestamp() as u64);
161
162 match validator.validate(&message).await {
163 ValidationResult::Accept => panic!("Expected reject, got accept"),
164 ValidationResult::Reject(reason) => {
165 assert!(reason.contains("has expired"));
166 }
167 }
168 }
169
170 #[tokio::test]
171 async fn test_very_large_timestamp() {
172 let validator = TimestampValidator::new(60);
173 let mut message = PlainMessage::new(
174 "test_msg_2".to_string(),
175 "test_type".to_string(),
176 serde_json::json!({}),
177 "did:example:sender".to_string(),
178 )
179 .with_recipient("did:example:receiver");
180
181 message.created_time = Some(u64::MAX);
183
184 match validator.validate(&message).await {
186 ValidationResult::Accept => panic!("Expected reject, got accept"),
187 ValidationResult::Reject(reason) => {
188 assert!(reason.contains("too far in the future"));
189 }
190 }
191 }
192}