Skip to main content

tap_node/validation/
uniqueness_validator.rs

1//! Message uniqueness validation
2
3use super::{MessageValidator, ValidationResult};
4use crate::storage::Storage;
5use async_trait::async_trait;
6use std::sync::Arc;
7use tap_msg::didcomm::PlainMessage;
8
9/// Validator that ensures message uniqueness
10///
11/// This validator checks that we haven't already received a message
12/// with the same ID, preventing replay attacks and duplicate processing.
13pub struct UniquenessValidator {
14    storage: Arc<Storage>,
15}
16
17impl UniquenessValidator {
18    /// Create a new uniqueness validator
19    pub fn new(storage: Arc<Storage>) -> Self {
20        Self { storage }
21    }
22}
23
24#[async_trait]
25impl MessageValidator for UniquenessValidator {
26    async fn validate(&self, message: &PlainMessage) -> ValidationResult {
27        // Check if message already exists in storage
28        match self.storage.get_message_by_id(&message.id).await {
29            Ok(Some(_)) => {
30                // Message already exists
31                ValidationResult::Reject(format!(
32                    "Duplicate message: message with ID {} already processed",
33                    message.id
34                ))
35            }
36            Ok(None) => {
37                // Message is unique
38                ValidationResult::Accept
39            }
40            Err(e) => {
41                // Storage error - reject to be safe
42                ValidationResult::Reject(format!("Unable to verify message uniqueness: {}", e))
43            }
44        }
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use crate::storage::MessageDirection;
52    use tempfile::tempdir;
53
54    #[tokio::test]
55    async fn test_unique_message() {
56        let dir = tempdir().unwrap();
57        let storage = Arc::new(
58            Storage::new(Some(dir.path().join("test.db")))
59                .await
60                .unwrap(),
61        );
62        let validator = UniquenessValidator::new(storage);
63
64        let message = PlainMessage::new(
65            "test_msg_1".to_string(),
66            "test_type".to_string(),
67            serde_json::json!({}),
68            "did:example:sender".to_string(),
69        )
70        .with_recipient("did:example:receiver");
71
72        match validator.validate(&message).await {
73            ValidationResult::Accept => {} // Expected
74            ValidationResult::Reject(reason) => panic!("Expected accept, got reject: {}", reason),
75        }
76    }
77
78    #[tokio::test]
79    async fn test_duplicate_message() {
80        let dir = tempdir().unwrap();
81        let storage = Arc::new(
82            Storage::new(Some(dir.path().join("test.db")))
83                .await
84                .unwrap(),
85        );
86        let validator = UniquenessValidator::new(storage.clone());
87
88        let message = PlainMessage::new(
89            "test_msg_1".to_string(),
90            "test_type".to_string(),
91            serde_json::json!({}),
92            "did:example:sender".to_string(),
93        )
94        .with_recipient("did:example:receiver");
95
96        // Store the message first
97        storage
98            .log_message(&message, MessageDirection::Incoming)
99            .await
100            .unwrap();
101
102        // Now validate - should be rejected as duplicate
103        match validator.validate(&message).await {
104            ValidationResult::Accept => panic!("Expected reject, got accept"),
105            ValidationResult::Reject(reason) => {
106                assert!(reason.contains("Duplicate message"));
107                assert!(reason.contains(&message.id));
108            }
109        }
110    }
111}