tap_node/message/
trust_ping_processor.rs

1//! Trust Ping Protocol Processor
2//!
3//! Handles automatic Trust Ping responses according to DIDComm 2.0 specification
4
5use crate::error::{Error, Result};
6use crate::event::EventBus;
7use crate::message::processor::PlainMessageProcessor;
8use async_trait::async_trait;
9use serde_json;
10use std::collections::HashMap;
11use std::sync::Arc;
12use tap_msg::didcomm::PlainMessage;
13use tap_msg::message::tap_message_trait::TapMessageBody;
14use tap_msg::message::{TrustPing, TrustPingResponse};
15
16/// Processor that automatically handles Trust Ping messages
17pub struct TrustPingProcessor {
18    /// Optional event bus for publishing response events
19    event_bus: Option<Arc<EventBus>>,
20}
21
22// Manual Debug implementation
23impl std::fmt::Debug for TrustPingProcessor {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        f.debug_struct("TrustPingProcessor")
26            .field("event_bus", &self.event_bus.is_some())
27            .finish()
28    }
29}
30
31// Manual Clone implementation
32impl Clone for TrustPingProcessor {
33    fn clone(&self) -> Self {
34        Self {
35            event_bus: self.event_bus.clone(),
36        }
37    }
38}
39
40impl Default for TrustPingProcessor {
41    fn default() -> Self {
42        Self::new()
43    }
44}
45
46impl TrustPingProcessor {
47    /// Create a new Trust Ping processor
48    pub fn new() -> Self {
49        Self { event_bus: None }
50    }
51
52    /// Create a new Trust Ping processor with an event bus for publishing responses
53    pub fn with_event_bus(event_bus: Arc<EventBus>) -> Self {
54        Self {
55            event_bus: Some(event_bus),
56        }
57    }
58
59    /// Generate a Trust Ping response for a given Trust Ping message
60    fn generate_ping_response(ping_message: &PlainMessage) -> Result<PlainMessage> {
61        // Parse the ping from the message body to validate it
62        let _ping: TrustPing = serde_json::from_value(ping_message.body.clone())
63            .map_err(|e| Error::Serialization(format!("Failed to parse TrustPing: {}", e)))?;
64
65        // Create a response with the same thread ID
66        let response =
67            TrustPingResponse::with_comment(ping_message.id.clone(), "Pong!".to_string());
68
69        // Create the response PlainMessage
70        let response_message = PlainMessage {
71            id: uuid::Uuid::new_v4().to_string(),
72            typ: "application/didcomm-plain+json".to_string(),
73            type_: TrustPingResponse::message_type().to_string(),
74            body: serde_json::to_value(&response).map_err(|e| {
75                Error::Serialization(format!("Failed to serialize response: {}", e))
76            })?,
77            from: ping_message.to[0].clone(), // Respond from the first recipient
78            to: vec![ping_message.from.clone()], // Send back to original sender
79            thid: Some(ping_message.id.clone()), // Set thread ID to original message ID
80            pthid: None,
81            extra_headers: HashMap::new(),
82            attachments: None,
83            created_time: Some(chrono::Utc::now().timestamp_millis() as u64),
84            expires_time: None,
85            from_prior: None,
86        };
87
88        Ok(response_message)
89    }
90
91    /// Check if a message is a Trust Ping that requests a response
92    fn should_respond_to_ping(message: &PlainMessage) -> bool {
93        if message.type_ != TrustPing::message_type() {
94            return false;
95        }
96
97        // Parse the message body to check if response is requested
98        if let Ok(ping) = serde_json::from_value::<TrustPing>(message.body.clone()) {
99            ping.response_requested
100        } else {
101            false
102        }
103    }
104}
105
106#[async_trait]
107impl PlainMessageProcessor for TrustPingProcessor {
108    async fn process_incoming(&self, message: PlainMessage) -> Result<Option<PlainMessage>> {
109        // Check if this is a Trust Ping that needs a response
110        if Self::should_respond_to_ping(&message) {
111            log::debug!(
112                "Received Trust Ping from {}, generating response",
113                message.from
114            );
115
116            // Generate and publish the response
117            match Self::generate_ping_response(&message) {
118                Ok(response) => {
119                    log::debug!("Generated Trust Ping response for message {}", message.id);
120
121                    // Publish the response as a message sent event if event bus is available
122                    if let Some(ref event_bus) = self.event_bus {
123                        // Extract the sender and recipient
124                        let from = response.from.clone();
125                        let to = response.to.first().cloned().unwrap_or_default();
126
127                        // Publish the event
128                        event_bus.publish_message_sent(response, from, to).await;
129                        log::info!("Successfully published Trust Ping response via event bus");
130                    } else {
131                        // No event bus configured, just log the response
132                        log::info!("Trust Ping response generated (no event bus configured): id={}, to={:?}", response.id, response.to);
133                    }
134                }
135                Err(e) => {
136                    log::warn!("Failed to generate Trust Ping response: {}", e);
137                }
138            }
139        }
140
141        // Always pass the message through unchanged
142        Ok(Some(message))
143    }
144
145    async fn process_outgoing(&self, message: PlainMessage) -> Result<Option<PlainMessage>> {
146        // No special processing for outgoing messages
147        Ok(Some(message))
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154    use tap_msg::message::TrustPing;
155
156    #[test]
157    fn test_should_respond_to_ping() {
158        // Create a Trust Ping message that requests response
159        let ping = TrustPing::new().response_requested(true);
160        let ping_message = PlainMessage {
161            id: "ping-123".to_string(),
162            typ: "application/didcomm-plain+json".to_string(),
163            type_: TrustPing::message_type().to_string(),
164            body: serde_json::to_value(&ping).unwrap(),
165            from: "did:example:sender".to_string(),
166            to: vec!["did:example:recipient".to_string()],
167            thid: None,
168            pthid: None,
169            extra_headers: HashMap::new(),
170            attachments: None,
171            created_time: Some(chrono::Utc::now().timestamp_millis() as u64),
172            expires_time: None,
173            from_prior: None,
174        };
175
176        assert!(TrustPingProcessor::should_respond_to_ping(&ping_message));
177
178        // Test with response_requested = false
179        let ping_no_response = TrustPing::new().response_requested(false);
180        let ping_message_no_response = PlainMessage {
181            id: "ping-456".to_string(),
182            typ: "application/didcomm-plain+json".to_string(),
183            type_: TrustPing::message_type().to_string(),
184            body: serde_json::to_value(&ping_no_response).unwrap(),
185            from: "did:example:sender".to_string(),
186            to: vec!["did:example:recipient".to_string()],
187            thid: None,
188            pthid: None,
189            extra_headers: HashMap::new(),
190            attachments: None,
191            created_time: Some(chrono::Utc::now().timestamp_millis() as u64),
192            expires_time: None,
193            from_prior: None,
194        };
195
196        assert!(!TrustPingProcessor::should_respond_to_ping(
197            &ping_message_no_response
198        ));
199    }
200
201    #[test]
202    fn test_generate_ping_response() {
203        let ping = TrustPing::with_comment("Hello!".to_string()).response_requested(true);
204
205        let ping_message = PlainMessage {
206            id: "ping-123".to_string(),
207            typ: "application/didcomm-plain+json".to_string(),
208            type_: TrustPing::message_type().to_string(),
209            body: serde_json::to_value(&ping).unwrap(),
210            from: "did:example:sender".to_string(),
211            to: vec!["did:example:recipient".to_string()],
212            thid: None,
213            pthid: None,
214            extra_headers: HashMap::new(),
215            attachments: None,
216            created_time: Some(chrono::Utc::now().timestamp_millis() as u64),
217            expires_time: None,
218            from_prior: None,
219        };
220
221        let response = TrustPingProcessor::generate_ping_response(&ping_message).unwrap();
222
223        assert_eq!(response.type_, TrustPingResponse::message_type());
224        assert_eq!(response.from, "did:example:recipient");
225        assert_eq!(response.to, vec!["did:example:sender"]);
226        assert_eq!(response.thid, Some("ping-123".to_string()));
227
228        // Verify the response body
229        let response_body: TrustPingResponse = serde_json::from_value(response.body).unwrap();
230        assert_eq!(response_body.thread_id, "ping-123");
231        assert_eq!(response_body.comment, Some("Pong!".to_string()));
232    }
233}