phalanx_crypto/
message.rs

1//! Message types and handling for Phalanx Protocol
2
3use crate::{
4    error::{PhalanxError, Result},
5    identity::{Identity, PublicKey},
6    crypto::{SymmetricKey, EncryptedData, hash_multiple},
7};
8use ed25519_dalek::Signature;
9use bytes::Bytes;
10use std::time::{SystemTime, UNIX_EPOCH};
11
12#[cfg(feature = "serde")]
13use serde::{Serialize, Deserialize};
14
15/// Message types in the Phalanx protocol
16#[derive(Debug, Clone, PartialEq, Eq)]
17#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
18pub enum MessageType {
19    /// Regular text message
20    Text,
21    /// System/control message
22    System,
23    /// Key rotation message
24    KeyRotation,
25    /// Member join notification
26    MemberJoin,
27    /// Member leave notification
28    MemberLeave,
29    /// Heartbeat/keepalive message
30    Heartbeat,
31}
32
33/// Encrypted group message with full cryptographic protection
34#[derive(Debug, Clone)]
35#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
36pub struct GroupMessage {
37    /// Message version for protocol evolution
38    pub version: u8,
39    /// Sender's public key
40    pub sender: PublicKey,
41    /// Message type
42    pub message_type: MessageType,
43    /// Message sequence number
44    pub sequence: u64,
45    /// Timestamp (Unix epoch seconds)
46    pub timestamp: u64,
47    /// Encrypted message content
48    pub encrypted_content: EncryptedData,
49    /// Digital signature of the entire message
50    pub signature: Signature,
51    /// Message ID (hash of content)
52    pub message_id: [u8; 32],
53}
54
55/// Plaintext message content before encryption
56#[derive(Debug, Clone, PartialEq, Eq)]
57#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
58pub struct MessageContent {
59    /// The actual message text or data
60    pub data: Bytes,
61    /// Optional reply-to message ID
62    pub reply_to: Option<[u8; 32]>,
63    /// Optional thread ID for threading support
64    pub thread_id: Option<[u8; 32]>,
65    /// Message metadata (arbitrary key-value pairs)
66    pub metadata: std::collections::HashMap<String, String>,
67}
68
69/// Encrypted message envelope for wire transmission
70#[derive(Debug, Clone)]
71#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
72pub struct EncryptedMessage {
73    /// Protocol version
74    pub version: u8,
75    /// Encrypted group message
76    pub encrypted_data: EncryptedData,
77    /// Sender identification (not encrypted for routing)
78    pub sender_id: [u8; 32],
79    /// Message timestamp
80    pub timestamp: u64,
81    /// Message sequence number
82    pub sequence: u64,
83}
84
85impl GroupMessage {
86    /// Create a new group message
87    pub fn new(
88        sender: &Identity,
89        message_type: MessageType,
90        content: &MessageContent,
91        sequence: u64,
92        group_key: &SymmetricKey,
93    ) -> Result<Self> {
94        let timestamp = SystemTime::now()
95            .duration_since(UNIX_EPOCH)
96            .map_err(|e| PhalanxError::crypto(format!("System time error: {}", e)))?
97            .as_secs();
98        
99        let sender_public = sender.public_key();
100        
101        // Serialize message content
102        let content_bytes = Self::serialize_content(content)?;
103        
104        // Create AAD (Additional Authenticated Data)
105        let aad = Self::create_aad(&sender_public, message_type.clone(), sequence, timestamp);
106        
107        // Encrypt the content
108        let encrypted_content = group_key.encrypt(&content_bytes, &aad)?;
109        
110        // Calculate message ID
111        let message_id = hash_multiple(&[
112            &sender_public.id(),
113            &sequence.to_be_bytes(),
114            &timestamp.to_be_bytes(),
115            &encrypted_content.ciphertext,
116        ]);
117        
118        // Create signature payload
119        let signature_data = Self::create_signature_data(
120            &sender_public,
121            &message_type,
122            sequence,
123            timestamp,
124            &encrypted_content,
125            &message_id,
126        );
127        
128        // Sign the message
129        let signature = sender.sign(&signature_data);
130        
131        Ok(Self {
132            version: crate::constants::PROTOCOL_VERSION,
133            sender: sender_public,
134            message_type,
135            sequence,
136            timestamp,
137            encrypted_content,
138            signature,
139            message_id,
140        })
141    }
142    
143    /// Decrypt and verify a group message
144    pub fn decrypt(&self, group_key: &SymmetricKey) -> Result<MessageContent> {
145        // Verify signature first
146        self.verify_signature()?;
147        
148        // Create AAD for decryption
149        let aad = Self::create_aad(&self.sender, self.message_type.clone(), self.sequence, self.timestamp);
150        
151        // Decrypt content
152        let decrypted_bytes = group_key.decrypt(&self.encrypted_content, &aad)?;
153        
154        // Deserialize content
155        Self::deserialize_content(&decrypted_bytes)
156    }
157    
158    /// Verify the message signature
159    pub fn verify_signature(&self) -> Result<()> {
160        let signature_data = Self::create_signature_data(
161            &self.sender,
162            &self.message_type,
163            self.sequence,
164            self.timestamp,
165            &self.encrypted_content,
166            &self.message_id,
167        );
168        
169        self.sender.verify(&signature_data, &self.signature)
170    }
171    
172    /// Check if this message is from a specific sender
173    pub fn is_from(&self, public_key: &PublicKey) -> bool {
174        self.sender.id() == public_key.id()
175    }
176    
177    /// Get the age of this message in seconds
178    pub fn age_seconds(&self) -> u64 {
179        SystemTime::now()
180            .duration_since(UNIX_EPOCH)
181            .map(|d| d.as_secs())
182            .unwrap_or(0)
183            .saturating_sub(self.timestamp)
184    }
185    
186    /// Create Additional Authenticated Data for encryption
187    fn create_aad(sender: &PublicKey, msg_type: MessageType, sequence: u64, timestamp: u64) -> Vec<u8> {
188        let mut aad = Vec::new();
189        aad.extend_from_slice(&sender.id());
190        aad.push(msg_type as u8);
191        aad.extend_from_slice(&sequence.to_be_bytes());
192        aad.extend_from_slice(&timestamp.to_be_bytes());
193        aad.extend_from_slice(b"PHALANX_MSG_V1");
194        aad
195    }
196    
197    /// Create signature data
198    fn create_signature_data(
199        sender: &PublicKey,
200        msg_type: &MessageType,
201        sequence: u64,
202        timestamp: u64,
203        encrypted_content: &EncryptedData,
204        message_id: &[u8; 32],
205    ) -> Vec<u8> {
206        let mut sig_data = Vec::new();
207        sig_data.push(crate::constants::PROTOCOL_VERSION);
208        sig_data.extend_from_slice(&sender.id());
209        sig_data.push(msg_type.clone() as u8);
210        sig_data.extend_from_slice(&sequence.to_be_bytes());
211        sig_data.extend_from_slice(&timestamp.to_be_bytes());
212        sig_data.extend_from_slice(&encrypted_content.ciphertext);
213        sig_data.extend_from_slice(&encrypted_content.nonce);
214        sig_data.extend_from_slice(&encrypted_content.aad_hash);
215        sig_data.extend_from_slice(message_id);
216        sig_data.extend_from_slice(b"PHALANX_SIG_V1");
217        sig_data
218    }
219    
220    /// Serialize message content to bytes
221    #[cfg(feature = "serde")]
222    fn serialize_content(content: &MessageContent) -> Result<Vec<u8>> {
223        serde_json::to_vec(content)
224            .map_err(|e| PhalanxError::protocol(format!("Content serialization failed: {}", e)))
225    }
226    
227    /// Serialize message content to bytes (without serde)
228    #[cfg(not(feature = "serde"))]
229    fn serialize_content(content: &MessageContent) -> Result<Vec<u8>> {
230        // Simple binary format without serde
231        let mut bytes = Vec::new();
232        
233        // Data length and data
234        let data_len = content.data.len() as u32;
235        bytes.extend_from_slice(&data_len.to_be_bytes());
236        bytes.extend_from_slice(&content.data);
237        
238        // Reply-to (32 bytes if present, or 0 marker)
239        if let Some(reply_to) = &content.reply_to {
240            bytes.push(1); // Present marker
241            bytes.extend_from_slice(reply_to);
242        } else {
243            bytes.push(0); // Not present marker
244        }
245        
246        // Thread ID (32 bytes if present, or 0 marker)
247        if let Some(thread_id) = &content.thread_id {
248            bytes.push(1); // Present marker
249            bytes.extend_from_slice(thread_id);
250        } else {
251            bytes.push(0); // Not present marker
252        }
253        
254        // Metadata (simplified - just serialize as JSON-like string)
255        let metadata_str = format!("{:?}", content.metadata);
256        let metadata_bytes = metadata_str.as_bytes();
257        let metadata_len = metadata_bytes.len() as u32;
258        bytes.extend_from_slice(&metadata_len.to_be_bytes());
259        bytes.extend_from_slice(metadata_bytes);
260        
261        Ok(bytes)
262    }
263    
264    /// Deserialize message content from bytes
265    #[cfg(feature = "serde")]
266    fn deserialize_content(bytes: &[u8]) -> Result<MessageContent> {
267        serde_json::from_slice(bytes)
268            .map_err(|e| PhalanxError::protocol(format!("Content deserialization failed: {}", e)))
269    }
270    
271    /// Deserialize message content from bytes (without serde)
272    #[cfg(not(feature = "serde"))]
273    fn deserialize_content(bytes: &[u8]) -> Result<MessageContent> {
274        if bytes.len() < 4 {
275            return Err(PhalanxError::protocol("Invalid content format"));
276        }
277        
278        let mut pos = 0;
279        
280        // Read data length and data
281        let data_len = u32::from_be_bytes([bytes[pos], bytes[pos+1], bytes[pos+2], bytes[pos+3]]) as usize;
282        pos += 4;
283        
284        if pos + data_len > bytes.len() {
285            return Err(PhalanxError::protocol("Invalid data length"));
286        }
287        
288        let data = Bytes::copy_from_slice(&bytes[pos..pos + data_len]);
289        pos += data_len;
290        
291        // Read reply-to
292        if pos >= bytes.len() {
293            return Err(PhalanxError::protocol("Truncated content"));
294        }
295        
296        let reply_to = if bytes[pos] == 1 {
297            pos += 1;
298            if pos + 32 > bytes.len() {
299                return Err(PhalanxError::protocol("Invalid reply-to"));
300            }
301            let mut reply_bytes = [0u8; 32];
302            reply_bytes.copy_from_slice(&bytes[pos..pos + 32]);
303            pos += 32;
304            Some(reply_bytes)
305        } else {
306            pos += 1;
307            None
308        };
309        
310        // Read thread ID
311        if pos >= bytes.len() {
312            return Err(PhalanxError::protocol("Truncated content"));
313        }
314        
315        let thread_id = if bytes[pos] == 1 {
316            pos += 1;
317            if pos + 32 > bytes.len() {
318                return Err(PhalanxError::protocol("Invalid thread ID"));
319            }
320            let mut thread_bytes = [0u8; 32];
321            thread_bytes.copy_from_slice(&bytes[pos..pos + 32]);
322            pos += 32;
323            Some(thread_bytes)
324        } else {
325            pos += 1;
326            None
327        };
328        
329        // Read metadata
330        if pos + 4 > bytes.len() {
331            return Err(PhalanxError::protocol("Truncated metadata length"));
332        }
333        
334        let metadata_len = u32::from_be_bytes([bytes[pos], bytes[pos+1], bytes[pos+2], bytes[pos+3]]) as usize;
335        pos += 4;
336        
337        if pos + metadata_len > bytes.len() {
338            return Err(PhalanxError::protocol("Truncated metadata"));
339        }
340        
341        // For simplicity, just create empty metadata in non-serde mode
342        let metadata = std::collections::HashMap::new();
343        
344        Ok(MessageContent {
345            data,
346            reply_to,
347            thread_id,
348            metadata,
349        })
350    }
351}
352
353impl MessageContent {
354    /// Create a new text message
355    pub fn text(message: impl Into<String>) -> Self {
356        Self {
357            data: Bytes::from(message.into()),
358            reply_to: None,
359            thread_id: None,
360            metadata: std::collections::HashMap::new(),
361        }
362    }
363    
364    /// Create a reply to another message
365    pub fn reply(message: impl Into<String>, reply_to: [u8; 32]) -> Self {
366        Self {
367            data: Bytes::from(message.into()),
368            reply_to: Some(reply_to),
369            thread_id: None,
370            metadata: std::collections::HashMap::new(),
371        }
372    }
373    
374    /// Add metadata to the message
375    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
376        self.metadata.insert(key.into(), value.into());
377        self
378    }
379    
380    /// Set thread ID for threaded conversations
381    pub fn with_thread(mut self, thread_id: [u8; 32]) -> Self {
382        self.thread_id = Some(thread_id);
383        self
384    }
385    
386    /// Get message as UTF-8 string
387    pub fn as_string(&self) -> Result<String> {
388        String::from_utf8(self.data.to_vec())
389            .map_err(|e| PhalanxError::protocol(format!("Invalid UTF-8: {}", e)))
390    }
391}
392
393// Convert MessageType to u8 for serialization
394impl From<MessageType> for u8 {
395    fn from(msg_type: MessageType) -> u8 {
396        match msg_type {
397            MessageType::Text => 0,
398            MessageType::System => 1,
399            MessageType::KeyRotation => 2,
400            MessageType::MemberJoin => 3,
401            MessageType::MemberLeave => 4,
402            MessageType::Heartbeat => 5,
403        }
404    }
405}
406
407impl TryFrom<u8> for MessageType {
408    type Error = PhalanxError;
409    
410    fn try_from(value: u8) -> Result<Self> {
411        match value {
412            0 => Ok(MessageType::Text),
413            1 => Ok(MessageType::System),
414            2 => Ok(MessageType::KeyRotation),
415            3 => Ok(MessageType::MemberJoin),
416            4 => Ok(MessageType::MemberLeave),
417            5 => Ok(MessageType::Heartbeat),
418            _ => Err(PhalanxError::protocol(format!("Unknown message type: {}", value))),
419        }
420    }
421}
422
423#[cfg(test)]
424mod tests {
425    use super::*;
426    use crate::crypto::SymmetricKey;
427    
428    #[test]
429    fn test_message_creation_and_decryption() {
430        let sender = Identity::generate();
431        let group_key = SymmetricKey::generate();
432        let content = MessageContent::text("Hello, world!");
433        
434        let message = GroupMessage::new(
435            &sender,
436            MessageType::Text,
437            &content,
438            1,
439            &group_key,
440        ).unwrap();
441        
442        let decrypted = message.decrypt(&group_key).unwrap();
443        assert_eq!(decrypted.as_string().unwrap(), "Hello, world!");
444    }
445    
446    #[test]
447    fn test_message_signature_verification() {
448        let sender = Identity::generate();
449        let group_key = SymmetricKey::generate();
450        let content = MessageContent::text("Test message");
451        
452        let message = GroupMessage::new(
453            &sender,
454            MessageType::Text,
455            &content,
456            1,
457            &group_key,
458        ).unwrap();
459        
460        assert!(message.verify_signature().is_ok());
461    }
462    
463    #[test]
464    fn test_reply_messages() {
465        let sender = Identity::generate();
466        let group_key = SymmetricKey::generate();
467        
468        let original_id = [1u8; 32];
469        let reply_content = MessageContent::reply("This is a reply", original_id);
470        
471        let message = GroupMessage::new(
472            &sender,
473            MessageType::Text,
474            &reply_content,
475            1,
476            &group_key,
477        ).unwrap();
478        
479        let decrypted = message.decrypt(&group_key).unwrap();
480        assert_eq!(decrypted.reply_to, Some(original_id));
481        assert_eq!(decrypted.as_string().unwrap(), "This is a reply");
482    }
483}