/ core / src / message / envelope.rs
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  }