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 let timestamp_seconds = if timestamp < 10_000_000_000 {
35 timestamp
37 } else {
38 timestamp / 1000
40 };
41
42 if timestamp_seconds > i64::MAX as u64 {
44 DateTime::parse_from_rfc3339("3000-01-01T00:00:00Z")
46 .unwrap()
47 .with_timezone(&Utc)
48 } else {
49 DateTime::from_timestamp(timestamp_seconds as i64, 0).unwrap_or_else(Utc::now)
50 }
51 }
52}
53
54#[async_trait]
55impl MessageValidator for TimestampValidator {
56 async fn validate(&self, message: &PlainMessage) -> ValidationResult {
57 let now = Utc::now();
58
59 if let Some(created_time) = message.created_time {
61 let created_dt = Self::timestamp_to_datetime(created_time);
62
63 let max_future = now + Duration::seconds(self.max_future_drift_secs);
65 if created_dt > max_future {
66 return ValidationResult::Reject(format!(
67 "Message created_time is too far in the future: {} (max allowed: {})",
68 created_dt, max_future
69 ));
70 }
71 }
72
73 if let Some(expires_time) = message.expires_time {
75 let expires_dt = Self::timestamp_to_datetime(expires_time);
76
77 if now > expires_dt {
79 return ValidationResult::Reject(format!(
80 "Message has expired at: {} (current time: {})",
81 expires_dt, now
82 ));
83 }
84 }
85
86 ValidationResult::Accept
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use chrono::Duration;
94
95 #[tokio::test]
96 async fn test_valid_timestamp() {
97 let validator = TimestampValidator::new(60);
98 let message = PlainMessage::new(
99 "test_msg_1".to_string(),
100 "test_type".to_string(),
101 serde_json::json!({}),
102 "did:example:sender".to_string(),
103 )
104 .with_recipient("did:example:receiver");
105
106 match validator.validate(&message).await {
109 ValidationResult::Accept => {} ValidationResult::Reject(reason) => panic!("Expected accept, got reject: {}", reason),
111 }
112 }
113
114 #[tokio::test]
115 async fn test_future_timestamp_within_drift() {
116 let validator = TimestampValidator::new(60);
117 let mut message = PlainMessage::new(
118 "test_msg_2".to_string(),
119 "test_type".to_string(),
120 serde_json::json!({}),
121 "did:example:sender".to_string(),
122 )
123 .with_recipient("did:example:receiver");
124
125 let future_time = Utc::now() + Duration::seconds(30);
127 message.created_time = Some(future_time.timestamp() as u64);
128
129 match validator.validate(&message).await {
130 ValidationResult::Accept => {} ValidationResult::Reject(reason) => panic!("Expected accept, got reject: {}", reason),
132 }
133 }
134
135 #[tokio::test]
136 async fn test_future_timestamp_exceeds_drift() {
137 let validator = TimestampValidator::new(60);
138 let mut message = PlainMessage::new(
139 "test_msg_2".to_string(),
140 "test_type".to_string(),
141 serde_json::json!({}),
142 "did:example:sender".to_string(),
143 )
144 .with_recipient("did:example:receiver");
145
146 let future_time = Utc::now() + Duration::seconds(120);
148 message.created_time = Some(future_time.timestamp() as u64);
149
150 match validator.validate(&message).await {
151 ValidationResult::Accept => panic!("Expected reject, got accept"),
152 ValidationResult::Reject(reason) => {
153 assert!(reason.contains("too far in the future"));
154 }
155 }
156 }
157
158 #[tokio::test]
159 async fn test_expired_message() {
160 let validator = TimestampValidator::new(60);
161 let mut message = PlainMessage::new(
162 "test_msg_2".to_string(),
163 "test_type".to_string(),
164 serde_json::json!({}),
165 "did:example:sender".to_string(),
166 )
167 .with_recipient("did:example:receiver");
168
169 let expired_time = Utc::now() - Duration::seconds(60);
171 message.expires_time = Some(expired_time.timestamp() as u64);
172
173 match validator.validate(&message).await {
174 ValidationResult::Accept => panic!("Expected reject, got accept"),
175 ValidationResult::Reject(reason) => {
176 assert!(reason.contains("has expired"));
177 }
178 }
179 }
180
181 #[tokio::test]
182 async fn test_very_large_timestamp() {
183 let validator = TimestampValidator::new(60);
184 let mut message = PlainMessage::new(
185 "test_msg_2".to_string(),
186 "test_type".to_string(),
187 serde_json::json!({}),
188 "did:example:sender".to_string(),
189 )
190 .with_recipient("did:example:receiver");
191
192 message.created_time = Some(32503680000000);
195
196 match validator.validate(&message).await {
198 ValidationResult::Accept => panic!("Expected reject, got accept"),
199 ValidationResult::Reject(reason) => {
200 assert!(reason.contains("too far in the future"));
201 }
202 }
203 }
204
205 #[tokio::test]
206 async fn test_timestamp_milliseconds() {
207 let validator = TimestampValidator::new(60);
208 let mut message = PlainMessage::new(
209 "test_msg_3".to_string(),
210 "test_type".to_string(),
211 serde_json::json!({}),
212 "did:example:sender".to_string(),
213 )
214 .with_recipient("did:example:receiver");
215
216 message.created_time = Some(Utc::now().timestamp_millis() as u64);
218
219 match validator.validate(&message).await {
220 ValidationResult::Accept => {} ValidationResult::Reject(reason) => panic!("Expected accept, got reject: {}", reason),
222 }
223 }
224
225 #[tokio::test]
226 async fn test_future_timestamp_milliseconds_exceeds_drift() {
227 let validator = TimestampValidator::new(60);
228 let mut message = PlainMessage::new(
229 "test_msg_4".to_string(),
230 "test_type".to_string(),
231 serde_json::json!({}),
232 "did:example:sender".to_string(),
233 )
234 .with_recipient("did:example:receiver");
235
236 let future_time = Utc::now() + Duration::seconds(120);
238 message.created_time = Some(future_time.timestamp_millis() as u64);
239
240 match validator.validate(&message).await {
241 ValidationResult::Accept => panic!("Expected reject, got accept"),
242 ValidationResult::Reject(reason) => {
243 assert!(reason.contains("too far in the future"));
244 }
245 }
246 }
247}