envelope.rs
1 //! Message envelope for multi-transport routing. 2 //! 3 //! The `MessageEnvelope` wraps encrypted messages with routing metadata, 4 //! enabling messages to be delivered over different transport protocols 5 //! while maintaining a consistent format. 6 //! 7 //! # Structure 8 //! 9 //! ```text 10 //! ┌─────────────────────────────────────────────────────────┐ 11 //! │ MessageEnvelope │ 12 //! ├─────────────────────────────────────────────────────────┤ 13 //! │ version: u8 (protocol version) │ 14 //! │ sender_identity: [u8; 32] (Ed25519 public key) │ 15 //! │ recipient_identity: [u8; 32] (Ed25519 public key) │ 16 //! │ timestamp: u64 (Unix timestamp) │ 17 //! │ transport_hint: TransportType (preferred transport) │ 18 //! │ payload: EncryptedMessage (the actual message) │ 19 //! └─────────────────────────────────────────────────────────┘ 20 //! ``` 21 //! 22 //! # Usage 23 //! 24 //! ```ignore 25 //! use dead_drop_core::message::MessageEnvelope; 26 //! use dead_drop_core::transport::TransportType; 27 //! 28 //! // Create envelope from an encrypted message 29 //! let envelope = MessageEnvelope::new( 30 //! sender_identity, 31 //! recipient_identity, 32 //! TransportType::Any, 33 //! encrypted_message, 34 //! ); 35 //! 36 //! // Serialize for transmission 37 //! let bytes = envelope.to_bytes()?; 38 //! 39 //! // Deserialize on receipt 40 //! let received = MessageEnvelope::from_bytes(&bytes)?; 41 //! ``` 42 43 use serde::{Deserialize, Serialize}; 44 45 use crate::error::{DeadDropError, Result}; 46 use crate::protocol::messages::EncryptedMessage; 47 use crate::transport::TransportType; 48 49 /// Current envelope protocol version. 50 pub const ENVELOPE_VERSION: u8 = 1; 51 52 /// Message envelope for transport-agnostic routing. 53 /// 54 /// The envelope wraps an `EncryptedMessage` with additional metadata 55 /// needed for routing across different transport protocols. 56 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 57 pub struct MessageEnvelope { 58 /// Protocol version for future compatibility. 59 pub version: u8, 60 61 /// Sender's Ed25519 identity public key. 62 /// 63 /// Used to verify the message signature and identify the sender. 64 pub sender_identity: [u8; 32], 65 66 /// Recipient's Ed25519 identity public key. 67 /// 68 /// Used for routing and to verify the message is for us. 69 pub recipient_identity: [u8; 32], 70 71 /// Unix timestamp (seconds) when the envelope was created. 72 /// 73 /// This is the envelope creation time, which may differ from 74 /// the message timestamp in the payload. 75 pub timestamp: u64, 76 77 /// Hint about which transport the sender prefers for replies. 78 /// 79 /// The recipient can use this to choose how to respond, but 80 /// is not required to honor it. 81 pub transport_hint: TransportType, 82 83 /// The encrypted message payload. 84 pub payload: EncryptedMessage, 85 } 86 87 impl MessageEnvelope { 88 /// Create a new message envelope. 89 /// 90 /// # Arguments 91 /// 92 /// * `sender_identity` - Sender's Ed25519 public key 93 /// * `recipient_identity` - Recipient's Ed25519 public key 94 /// * `transport_hint` - Preferred transport for replies 95 /// * `payload` - The encrypted message 96 pub fn new( 97 sender_identity: [u8; 32], 98 recipient_identity: [u8; 32], 99 transport_hint: TransportType, 100 payload: EncryptedMessage, 101 ) -> Self { 102 Self { 103 version: ENVELOPE_VERSION, 104 sender_identity, 105 recipient_identity, 106 timestamp: chrono::Utc::now().timestamp() as u64, 107 transport_hint, 108 payload, 109 } 110 } 111 112 /// Create an envelope with a specific timestamp (for testing). 113 pub fn with_timestamp( 114 sender_identity: [u8; 32], 115 recipient_identity: [u8; 32], 116 transport_hint: TransportType, 117 payload: EncryptedMessage, 118 timestamp: u64, 119 ) -> Self { 120 Self { 121 version: ENVELOPE_VERSION, 122 sender_identity, 123 recipient_identity, 124 timestamp, 125 transport_hint, 126 payload, 127 } 128 } 129 130 /// Get the message ID from the payload. 131 pub fn message_id(&self) -> &[u8; 16] { 132 &self.payload.message_id 133 } 134 135 /// Check if this envelope is for the given recipient. 136 pub fn is_for(&self, recipient_identity: &[u8; 32]) -> bool { 137 &self.recipient_identity == recipient_identity 138 } 139 140 /// Check if this envelope is from the given sender. 141 pub fn is_from(&self, sender_identity: &[u8; 32]) -> bool { 142 &self.sender_identity == sender_identity 143 } 144 145 /// Check if the envelope has expired. 146 /// 147 /// # Arguments 148 /// 149 /// * `max_age_seconds` - Maximum age in seconds before expiration 150 pub fn is_expired(&self, max_age_seconds: u64) -> bool { 151 let now = chrono::Utc::now().timestamp() as u64; 152 now.saturating_sub(self.timestamp) > max_age_seconds 153 } 154 155 /// Validate the envelope structure. 156 /// 157 /// Checks: 158 /// - Version is supported 159 /// - Identities are not all zeros 160 /// - Timestamp is reasonable 161 pub fn validate(&self) -> Result<()> { 162 // Check version 163 if self.version != ENVELOPE_VERSION { 164 return Err(DeadDropError::InvalidInput(format!( 165 "Unsupported envelope version: {} (expected {})", 166 self.version, ENVELOPE_VERSION 167 ))); 168 } 169 170 // Check sender identity is not zero 171 if self.sender_identity == [0u8; 32] { 172 return Err(DeadDropError::InvalidInput( 173 "Sender identity cannot be all zeros".to_string() 174 )); 175 } 176 177 // Check recipient identity is not zero 178 if self.recipient_identity == [0u8; 32] { 179 return Err(DeadDropError::InvalidInput( 180 "Recipient identity cannot be all zeros".to_string() 181 )); 182 } 183 184 // Check timestamp is not in the far future (allow 5 min clock skew) 185 let now = chrono::Utc::now().timestamp() as u64; 186 if self.timestamp > now + 300 { 187 return Err(DeadDropError::InvalidInput( 188 "Envelope timestamp is in the future".to_string() 189 )); 190 } 191 192 Ok(()) 193 } 194 195 /// Serialize the envelope to bytes. 196 pub fn to_bytes(&self) -> Result<Vec<u8>> { 197 bincode::serialize(self).map_err(|e| DeadDropError::Serialization(e.to_string())) 198 } 199 200 /// Deserialize an envelope from bytes. 201 pub fn from_bytes(bytes: &[u8]) -> Result<Self> { 202 bincode::deserialize(bytes) 203 .map_err(|e| DeadDropError::Deserialization(format!("Invalid envelope: {}", e))) 204 } 205 } 206 207 impl std::fmt::Display for MessageEnvelope { 208 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 209 write!( 210 f, 211 "Envelope(v{}, from={}, to={}, hint={:?}, msg={})", 212 self.version, 213 hex::encode(&self.sender_identity[..4]), 214 hex::encode(&self.recipient_identity[..4]), 215 self.transport_hint, 216 hex::encode(&self.payload.message_id[..4]) 217 ) 218 } 219 } 220 221 #[cfg(test)] 222 mod tests { 223 use super::*; 224 use crate::crypto::keys::{IdentityKeyPair, ExchangeKeyPair}; 225 use crate::protocol::messages::{PlaintextMessage, encrypt_message}; 226 227 fn create_test_envelope() -> MessageEnvelope { 228 let sender_identity = IdentityKeyPair::generate(); 229 let recipient_exchange = ExchangeKeyPair::generate(); 230 let recipient_identity = [0xAA; 32]; // Fake recipient identity 231 232 let plaintext = PlaintextMessage::text("Test message"); 233 let encrypted = encrypt_message( 234 &plaintext, 235 &sender_identity, 236 &recipient_exchange.public_bytes(), 237 ).unwrap(); 238 239 MessageEnvelope::new( 240 sender_identity.public_bytes(), 241 recipient_identity, 242 TransportType::BLE, 243 encrypted, 244 ) 245 } 246 247 #[test] 248 fn test_envelope_new() { 249 let envelope = create_test_envelope(); 250 assert_eq!(envelope.version, ENVELOPE_VERSION); 251 assert_eq!(envelope.transport_hint, TransportType::BLE); 252 } 253 254 #[test] 255 fn test_envelope_is_for() { 256 let envelope = create_test_envelope(); 257 assert!(envelope.is_for(&envelope.recipient_identity)); 258 assert!(!envelope.is_for(&[0xBB; 32])); 259 } 260 261 #[test] 262 fn test_envelope_is_from() { 263 let envelope = create_test_envelope(); 264 assert!(envelope.is_from(&envelope.sender_identity)); 265 assert!(!envelope.is_from(&[0xBB; 32])); 266 } 267 268 #[test] 269 fn test_envelope_is_expired() { 270 let old_timestamp = (chrono::Utc::now().timestamp() - 3600) as u64; // 1 hour ago 271 let sender_identity = IdentityKeyPair::generate(); 272 let recipient_exchange = ExchangeKeyPair::generate(); 273 274 let plaintext = PlaintextMessage::text("Test"); 275 let encrypted = encrypt_message( 276 &plaintext, 277 &sender_identity, 278 &recipient_exchange.public_bytes(), 279 ).unwrap(); 280 281 let envelope = MessageEnvelope::with_timestamp( 282 sender_identity.public_bytes(), 283 [0xAA; 32], 284 TransportType::BLE, 285 encrypted, 286 old_timestamp, 287 ); 288 289 // Expired after 30 minutes 290 assert!(envelope.is_expired(1800)); 291 292 // Not expired after 2 hours 293 assert!(!envelope.is_expired(7200)); 294 } 295 296 #[test] 297 fn test_envelope_validate_success() { 298 let envelope = create_test_envelope(); 299 assert!(envelope.validate().is_ok()); 300 } 301 302 #[test] 303 fn test_envelope_validate_zero_sender() { 304 let mut envelope = create_test_envelope(); 305 envelope.sender_identity = [0u8; 32]; 306 assert!(envelope.validate().is_err()); 307 } 308 309 #[test] 310 fn test_envelope_validate_zero_recipient() { 311 let mut envelope = create_test_envelope(); 312 envelope.recipient_identity = [0u8; 32]; 313 assert!(envelope.validate().is_err()); 314 } 315 316 #[test] 317 fn test_envelope_validate_future_timestamp() { 318 let mut envelope = create_test_envelope(); 319 envelope.timestamp = (chrono::Utc::now().timestamp() + 3600) as u64; // 1 hour in future 320 assert!(envelope.validate().is_err()); 321 } 322 323 #[test] 324 fn test_envelope_validate_bad_version() { 325 let mut envelope = create_test_envelope(); 326 envelope.version = 99; 327 assert!(envelope.validate().is_err()); 328 } 329 330 #[test] 331 fn test_envelope_serialization() { 332 let original = create_test_envelope(); 333 let bytes = original.to_bytes().unwrap(); 334 let recovered = MessageEnvelope::from_bytes(&bytes).unwrap(); 335 336 assert_eq!(original.version, recovered.version); 337 assert_eq!(original.sender_identity, recovered.sender_identity); 338 assert_eq!(original.recipient_identity, recovered.recipient_identity); 339 assert_eq!(original.transport_hint, recovered.transport_hint); 340 assert_eq!(original.payload.message_id, recovered.payload.message_id); 341 } 342 343 #[test] 344 fn test_envelope_display() { 345 let envelope = create_test_envelope(); 346 let display = format!("{}", envelope); 347 assert!(display.contains("Envelope")); 348 assert!(display.contains("v1")); 349 assert!(display.contains("BLE")); 350 } 351 352 #[test] 353 fn test_envelope_message_id() { 354 let envelope = create_test_envelope(); 355 assert_eq!(envelope.message_id(), &envelope.payload.message_id); 356 } 357 }