tap_msg/message/trust_ping/
mod.rs

1//! Trust Ping Protocol Implementation
2//!
3//! Implementation of the DIDComm Trust Ping 2.0 protocol as specified at:
4//! https://identity.foundation/didcomm-messaging/spec/#trust-ping-protocol-20
5//!
6//! The Trust Ping protocol is used to verify connectivity and test the
7//! communication channel between DIDComm agents.
8
9use crate::error::{Error, Result};
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12use tap_msg_derive::TapMessage;
13
14pub const TRUST_PING_TYPE: &str = "https://didcomm.org/trust-ping/2.0/ping";
15pub const TRUST_PING_RESPONSE_TYPE: &str = "https://didcomm.org/trust-ping/2.0/ping-response";
16
17/// Trust Ping message for testing connectivity between agents
18///
19/// The Trust Ping protocol allows agents to test their ability to communicate
20/// and verify that the communication channel is working properly.
21#[derive(Debug, Clone, Serialize, Deserialize, TapMessage)]
22#[tap(
23    message_type = "https://didcomm.org/trust-ping/2.0/ping",
24    custom_validation
25)]
26pub struct TrustPing {
27    /// Whether a response is requested (defaults to true)
28    #[serde(default = "default_response_requested")]
29    pub response_requested: bool,
30
31    /// Optional comment or description for the ping
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub comment: Option<String>,
34
35    /// Additional metadata
36    #[serde(flatten)]
37    pub metadata: HashMap<String, serde_json::Value>,
38}
39
40/// Trust Ping Response message
41///
42/// Response to a Trust Ping message, confirming that the communication
43/// channel is working and the recipient is reachable.
44#[derive(Debug, Clone, Default, Serialize, Deserialize, TapMessage)]
45#[tap(
46    message_type = "https://didcomm.org/trust-ping/2.0/ping-response",
47    custom_validation
48)]
49pub struct TrustPingResponse {
50    /// Optional comment or description for the response
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub comment: Option<String>,
53
54    /// Thread ID referencing the original ping message
55    #[serde(default)]
56    pub thread_id: String,
57
58    /// Additional metadata
59    #[serde(flatten, default)]
60    pub metadata: HashMap<String, serde_json::Value>,
61}
62
63fn default_response_requested() -> bool {
64    true
65}
66
67impl Default for TrustPing {
68    fn default() -> Self {
69        Self {
70            response_requested: true,
71            comment: None,
72            metadata: HashMap::new(),
73        }
74    }
75}
76
77impl TrustPing {
78    /// Create a new Trust Ping message
79    pub fn new() -> Self {
80        Self::default()
81    }
82
83    /// Create a Trust Ping with a comment
84    pub fn with_comment(comment: String) -> Self {
85        Self {
86            comment: Some(comment),
87            response_requested: true,
88            metadata: HashMap::new(),
89        }
90    }
91
92    /// Set whether a response is requested
93    pub fn response_requested(mut self, requested: bool) -> Self {
94        self.response_requested = requested;
95        self
96    }
97
98    /// Add metadata
99    pub fn with_metadata(mut self, key: String, value: serde_json::Value) -> Self {
100        self.metadata.insert(key, value);
101        self
102    }
103
104    /// Custom validation for Trust Ping messages
105    pub fn validate_trustping(&self) -> Result<()> {
106        // Trust Ping messages are very simple and don't require much validation
107        // The only optional validation would be comment length if needed
108        if let Some(ref comment) = self.comment {
109            if comment.len() > 1000 {
110                return Err(Error::Validation(
111                    "Trust Ping comment exceeds maximum length of 1000 characters".to_string(),
112                ));
113            }
114        }
115
116        Ok(())
117    }
118}
119
120impl TrustPingResponse {
121    /// Create a new Trust Ping Response
122    pub fn new(thread_id: String) -> Self {
123        Self {
124            comment: None,
125            thread_id,
126            metadata: HashMap::new(),
127        }
128    }
129
130    /// Create a Trust Ping Response with a comment
131    pub fn with_comment(thread_id: String, comment: String) -> Self {
132        Self {
133            comment: Some(comment),
134            thread_id,
135            metadata: HashMap::new(),
136        }
137    }
138
139    /// Add metadata
140    pub fn with_metadata(mut self, key: String, value: serde_json::Value) -> Self {
141        self.metadata.insert(key, value);
142        self
143    }
144
145    /// Custom validation for Trust Ping Response messages
146    pub fn validate_trustpingresponse(&self) -> Result<()> {
147        if self.thread_id.is_empty() {
148            return Err(Error::Validation(
149                "Thread ID is required in Trust Ping Response".to_string(),
150            ));
151        }
152
153        // Validate comment length if present
154        if let Some(ref comment) = self.comment {
155            if comment.len() > 1000 {
156                return Err(Error::Validation(
157                    "Trust Ping Response comment exceeds maximum length of 1000 characters"
158                        .to_string(),
159                ));
160            }
161        }
162
163        Ok(())
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[test]
172    fn test_trust_ping_creation() {
173        let ping = TrustPing::new();
174        assert!(ping.response_requested);
175        assert!(ping.comment.is_none());
176        assert!(ping.metadata.is_empty());
177    }
178
179    #[test]
180    fn test_trust_ping_with_comment() {
181        let ping = TrustPing::with_comment("Testing connectivity".to_string());
182        assert_eq!(ping.comment, Some("Testing connectivity".to_string()));
183        assert!(ping.response_requested);
184    }
185
186    #[test]
187    fn test_trust_ping_no_response() {
188        let ping = TrustPing::new().response_requested(false);
189        assert!(!ping.response_requested);
190    }
191
192    #[test]
193    fn test_trust_ping_response_creation() {
194        let response = TrustPingResponse::new("thread-123".to_string());
195        assert_eq!(response.thread_id, "thread-123");
196        assert!(response.comment.is_none());
197        assert!(response.metadata.is_empty());
198    }
199
200    #[test]
201    fn test_trust_ping_response_with_comment() {
202        let response =
203            TrustPingResponse::with_comment("thread-123".to_string(), "Pong!".to_string());
204        assert_eq!(response.thread_id, "thread-123");
205        assert_eq!(response.comment, Some("Pong!".to_string()));
206    }
207
208    #[test]
209    fn test_trust_ping_validation() {
210        let ping = TrustPing::new();
211        assert!(ping.validate_trustping().is_ok());
212
213        let long_comment = "a".repeat(1001);
214        let ping_with_long_comment = TrustPing::with_comment(long_comment);
215        assert!(ping_with_long_comment.validate_trustping().is_err());
216    }
217
218    #[test]
219    fn test_trust_ping_response_validation() {
220        let response = TrustPingResponse::new("valid-thread-id".to_string());
221        assert!(response.validate_trustpingresponse().is_ok());
222
223        let response_empty_thread = TrustPingResponse::new("".to_string());
224        assert!(response_empty_thread.validate_trustpingresponse().is_err());
225
226        let long_comment = "a".repeat(1001);
227        let response_with_long_comment =
228            TrustPingResponse::with_comment("valid-thread-id".to_string(), long_comment);
229        assert!(response_with_long_comment
230            .validate_trustpingresponse()
231            .is_err());
232    }
233
234    #[test]
235    fn test_trust_ping_serialization() {
236        let ping = TrustPing::with_comment("Test".to_string())
237            .response_requested(false)
238            .with_metadata("test_key".to_string(), serde_json::json!("test_value"));
239
240        let serialized = serde_json::to_string(&ping).unwrap();
241        let deserialized: TrustPing = serde_json::from_str(&serialized).unwrap();
242
243        assert_eq!(ping.comment, deserialized.comment);
244        assert_eq!(ping.response_requested, deserialized.response_requested);
245        assert_eq!(ping.metadata, deserialized.metadata);
246    }
247
248    #[test]
249    fn test_trust_ping_response_serialization() {
250        let response =
251            TrustPingResponse::with_comment("thread-123".to_string(), "Pong!".to_string())
252                .with_metadata("test_key".to_string(), serde_json::json!("test_value"));
253
254        let serialized = serde_json::to_string(&response).unwrap();
255        let deserialized: TrustPingResponse = serde_json::from_str(&serialized).unwrap();
256
257        assert_eq!(response.comment, deserialized.comment);
258        assert_eq!(response.thread_id, deserialized.thread_id);
259        assert_eq!(response.metadata, deserialized.metadata);
260    }
261
262    #[test]
263    fn test_message_types() {
264        use crate::message::tap_message_trait::TapMessageBody;
265        assert_eq!(TrustPing::message_type(), TRUST_PING_TYPE);
266        assert_eq!(TrustPingResponse::message_type(), TRUST_PING_RESPONSE_TYPE);
267    }
268}