use crate::{
error::{PhalanxError, Result},
identity::{Identity, PublicKey},
crypto::{SymmetricKey, EncryptedData, hash_multiple},
};
use ed25519_dalek::Signature;
use bytes::Bytes;
use std::time::{SystemTime, UNIX_EPOCH};
#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MessageType {
Text,
System,
KeyRotation,
MemberJoin,
MemberLeave,
Heartbeat,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GroupMessage {
pub version: u8,
pub sender: PublicKey,
pub message_type: MessageType,
pub sequence: u64,
pub timestamp: u64,
pub encrypted_content: EncryptedData,
pub signature: Signature,
pub message_id: [u8; 32],
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct MessageContent {
pub data: Bytes,
pub reply_to: Option<[u8; 32]>,
pub thread_id: Option<[u8; 32]>,
pub metadata: std::collections::HashMap<String, String>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct EncryptedMessage {
pub version: u8,
pub encrypted_data: EncryptedData,
pub sender_id: [u8; 32],
pub timestamp: u64,
pub sequence: u64,
}
impl GroupMessage {
pub fn new(
sender: &Identity,
message_type: MessageType,
content: &MessageContent,
sequence: u64,
group_key: &SymmetricKey,
) -> Result<Self> {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|e| PhalanxError::crypto(format!("System time error: {}", e)))?
.as_secs();
let sender_public = sender.public_key();
let content_bytes = Self::serialize_content(content)?;
let aad = Self::create_aad(&sender_public, message_type.clone(), sequence, timestamp);
let encrypted_content = group_key.encrypt(&content_bytes, &aad)?;
let message_id = hash_multiple(&[
&sender_public.id(),
&sequence.to_be_bytes(),
×tamp.to_be_bytes(),
&encrypted_content.ciphertext,
]);
let signature_data = Self::create_signature_data(
&sender_public,
&message_type,
sequence,
timestamp,
&encrypted_content,
&message_id,
);
let signature = sender.sign(&signature_data);
Ok(Self {
version: crate::constants::PROTOCOL_VERSION,
sender: sender_public,
message_type,
sequence,
timestamp,
encrypted_content,
signature,
message_id,
})
}
pub fn decrypt(&self, group_key: &SymmetricKey) -> Result<MessageContent> {
self.verify_signature()?;
let aad = Self::create_aad(&self.sender, self.message_type.clone(), self.sequence, self.timestamp);
let decrypted_bytes = group_key.decrypt(&self.encrypted_content, &aad)?;
Self::deserialize_content(&decrypted_bytes)
}
pub fn verify_signature(&self) -> Result<()> {
let signature_data = Self::create_signature_data(
&self.sender,
&self.message_type,
self.sequence,
self.timestamp,
&self.encrypted_content,
&self.message_id,
);
self.sender.verify(&signature_data, &self.signature)
}
pub fn is_from(&self, public_key: &PublicKey) -> bool {
self.sender.id() == public_key.id()
}
pub fn age_seconds(&self) -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
.saturating_sub(self.timestamp)
}
fn create_aad(sender: &PublicKey, msg_type: MessageType, sequence: u64, timestamp: u64) -> Vec<u8> {
let mut aad = Vec::new();
aad.extend_from_slice(&sender.id());
aad.push(msg_type as u8);
aad.extend_from_slice(&sequence.to_be_bytes());
aad.extend_from_slice(×tamp.to_be_bytes());
aad.extend_from_slice(b"PHALANX_MSG_V1");
aad
}
fn create_signature_data(
sender: &PublicKey,
msg_type: &MessageType,
sequence: u64,
timestamp: u64,
encrypted_content: &EncryptedData,
message_id: &[u8; 32],
) -> Vec<u8> {
let mut sig_data = Vec::new();
sig_data.push(crate::constants::PROTOCOL_VERSION);
sig_data.extend_from_slice(&sender.id());
sig_data.push(msg_type.clone() as u8);
sig_data.extend_from_slice(&sequence.to_be_bytes());
sig_data.extend_from_slice(×tamp.to_be_bytes());
sig_data.extend_from_slice(&encrypted_content.ciphertext);
sig_data.extend_from_slice(&encrypted_content.nonce);
sig_data.extend_from_slice(&encrypted_content.aad_hash);
sig_data.extend_from_slice(message_id);
sig_data.extend_from_slice(b"PHALANX_SIG_V1");
sig_data
}
#[cfg(feature = "serde")]
fn serialize_content(content: &MessageContent) -> Result<Vec<u8>> {
serde_json::to_vec(content)
.map_err(|e| PhalanxError::protocol(format!("Content serialization failed: {}", e)))
}
#[cfg(not(feature = "serde"))]
fn serialize_content(content: &MessageContent) -> Result<Vec<u8>> {
let mut bytes = Vec::new();
let data_len = content.data.len() as u32;
bytes.extend_from_slice(&data_len.to_be_bytes());
bytes.extend_from_slice(&content.data);
if let Some(reply_to) = &content.reply_to {
bytes.push(1); bytes.extend_from_slice(reply_to);
} else {
bytes.push(0); }
if let Some(thread_id) = &content.thread_id {
bytes.push(1); bytes.extend_from_slice(thread_id);
} else {
bytes.push(0); }
let metadata_str = format!("{:?}", content.metadata);
let metadata_bytes = metadata_str.as_bytes();
let metadata_len = metadata_bytes.len() as u32;
bytes.extend_from_slice(&metadata_len.to_be_bytes());
bytes.extend_from_slice(metadata_bytes);
Ok(bytes)
}
#[cfg(feature = "serde")]
fn deserialize_content(bytes: &[u8]) -> Result<MessageContent> {
serde_json::from_slice(bytes)
.map_err(|e| PhalanxError::protocol(format!("Content deserialization failed: {}", e)))
}
#[cfg(not(feature = "serde"))]
fn deserialize_content(bytes: &[u8]) -> Result<MessageContent> {
if bytes.len() < 4 {
return Err(PhalanxError::protocol("Invalid content format"));
}
let mut pos = 0;
let data_len = u32::from_be_bytes([bytes[pos], bytes[pos+1], bytes[pos+2], bytes[pos+3]]) as usize;
pos += 4;
if pos + data_len > bytes.len() {
return Err(PhalanxError::protocol("Invalid data length"));
}
let data = Bytes::copy_from_slice(&bytes[pos..pos + data_len]);
pos += data_len;
if pos >= bytes.len() {
return Err(PhalanxError::protocol("Truncated content"));
}
let reply_to = if bytes[pos] == 1 {
pos += 1;
if pos + 32 > bytes.len() {
return Err(PhalanxError::protocol("Invalid reply-to"));
}
let mut reply_bytes = [0u8; 32];
reply_bytes.copy_from_slice(&bytes[pos..pos + 32]);
pos += 32;
Some(reply_bytes)
} else {
pos += 1;
None
};
if pos >= bytes.len() {
return Err(PhalanxError::protocol("Truncated content"));
}
let thread_id = if bytes[pos] == 1 {
pos += 1;
if pos + 32 > bytes.len() {
return Err(PhalanxError::protocol("Invalid thread ID"));
}
let mut thread_bytes = [0u8; 32];
thread_bytes.copy_from_slice(&bytes[pos..pos + 32]);
pos += 32;
Some(thread_bytes)
} else {
pos += 1;
None
};
if pos + 4 > bytes.len() {
return Err(PhalanxError::protocol("Truncated metadata length"));
}
let metadata_len = u32::from_be_bytes([bytes[pos], bytes[pos+1], bytes[pos+2], bytes[pos+3]]) as usize;
pos += 4;
if pos + metadata_len > bytes.len() {
return Err(PhalanxError::protocol("Truncated metadata"));
}
let metadata = std::collections::HashMap::new();
Ok(MessageContent {
data,
reply_to,
thread_id,
metadata,
})
}
}
impl MessageContent {
pub fn text(message: impl Into<String>) -> Self {
Self {
data: Bytes::from(message.into()),
reply_to: None,
thread_id: None,
metadata: std::collections::HashMap::new(),
}
}
pub fn reply(message: impl Into<String>, reply_to: [u8; 32]) -> Self {
Self {
data: Bytes::from(message.into()),
reply_to: Some(reply_to),
thread_id: None,
metadata: std::collections::HashMap::new(),
}
}
pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
pub fn with_thread(mut self, thread_id: [u8; 32]) -> Self {
self.thread_id = Some(thread_id);
self
}
pub fn as_string(&self) -> Result<String> {
String::from_utf8(self.data.to_vec())
.map_err(|e| PhalanxError::protocol(format!("Invalid UTF-8: {}", e)))
}
}
impl From<MessageType> for u8 {
fn from(msg_type: MessageType) -> u8 {
match msg_type {
MessageType::Text => 0,
MessageType::System => 1,
MessageType::KeyRotation => 2,
MessageType::MemberJoin => 3,
MessageType::MemberLeave => 4,
MessageType::Heartbeat => 5,
}
}
}
impl TryFrom<u8> for MessageType {
type Error = PhalanxError;
fn try_from(value: u8) -> Result<Self> {
match value {
0 => Ok(MessageType::Text),
1 => Ok(MessageType::System),
2 => Ok(MessageType::KeyRotation),
3 => Ok(MessageType::MemberJoin),
4 => Ok(MessageType::MemberLeave),
5 => Ok(MessageType::Heartbeat),
_ => Err(PhalanxError::protocol(format!("Unknown message type: {}", value))),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::SymmetricKey;
#[test]
fn test_message_creation_and_decryption() {
let sender = Identity::generate();
let group_key = SymmetricKey::generate();
let content = MessageContent::text("Hello, world!");
let message = GroupMessage::new(
&sender,
MessageType::Text,
&content,
1,
&group_key,
).unwrap();
let decrypted = message.decrypt(&group_key).unwrap();
assert_eq!(decrypted.as_string().unwrap(), "Hello, world!");
}
#[test]
fn test_message_signature_verification() {
let sender = Identity::generate();
let group_key = SymmetricKey::generate();
let content = MessageContent::text("Test message");
let message = GroupMessage::new(
&sender,
MessageType::Text,
&content,
1,
&group_key,
).unwrap();
assert!(message.verify_signature().is_ok());
}
#[test]
fn test_reply_messages() {
let sender = Identity::generate();
let group_key = SymmetricKey::generate();
let original_id = [1u8; 32];
let reply_content = MessageContent::reply("This is a reply", original_id);
let message = GroupMessage::new(
&sender,
MessageType::Text,
&reply_content,
1,
&group_key,
).unwrap();
let decrypted = message.decrypt(&group_key).unwrap();
assert_eq!(decrypted.reply_to, Some(original_id));
assert_eq!(decrypted.as_string().unwrap(), "This is a reply");
}
}