use crate::{Error, Result, Fingerprint};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum MessageType {
Text = 0,
Binary = 1,
File = 2,
Reaction = 3,
Edit = 4,
Delete = 5,
Typing = 6,
Read = 7,
System = 8,
Agent = 9,
}
impl TryFrom<u8> for MessageType {
type Error = Error;
fn try_from(value: u8) -> Result<Self> {
match value {
0 => Ok(Self::Text),
1 => Ok(Self::Binary),
2 => Ok(Self::File),
3 => Ok(Self::Reaction),
4 => Ok(Self::Edit),
5 => Ok(Self::Delete),
6 => Ok(Self::Typing),
7 => Ok(Self::Read),
8 => Ok(Self::System),
9 => Ok(Self::Agent),
_ => Err(Error::InvalidMessage(format!("Unknown message type: {}", value))),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
pub id: String,
pub msg_type: MessageType,
pub sender: String,
pub recipient: String,
pub timestamp: u64,
pub content: MessageContent,
pub reply_to: Option<String>,
pub metadata: MessageMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MessageContent {
Text(String),
Binary(Vec<u8>),
File {
name: String,
size: u64,
mime_type: String,
hash: String,
},
Reaction(String),
Edit {
message_id: String,
new_text: String,
},
Delete {
message_id: String,
},
Empty,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MessageMetadata {
pub pqc_encrypted: bool,
pub qrng_entropy: bool,
pub qkd_enhanced: bool,
pub agent_id: Option<String>,
pub mentions: Vec<String>,
pub tx_hash: Option<String>,
}
impl Message {
pub fn text(sender: &str, recipient: &str, text: impl Into<String>) -> Self {
Self {
id: Self::generate_id(),
msg_type: MessageType::Text,
sender: sender.to_string(),
recipient: recipient.to_string(),
timestamp: Self::now(),
content: MessageContent::Text(text.into()),
reply_to: None,
metadata: MessageMetadata::default(),
}
}
pub fn binary(sender: &str, recipient: &str, data: Vec<u8>) -> Self {
Self {
id: Self::generate_id(),
msg_type: MessageType::Binary,
sender: sender.to_string(),
recipient: recipient.to_string(),
timestamp: Self::now(),
content: MessageContent::Binary(data),
reply_to: None,
metadata: MessageMetadata::default(),
}
}
pub fn file(
sender: &str,
recipient: &str,
name: String,
size: u64,
mime_type: String,
hash: String,
) -> Self {
Self {
id: Self::generate_id(),
msg_type: MessageType::File,
sender: sender.to_string(),
recipient: recipient.to_string(),
timestamp: Self::now(),
content: MessageContent::File {
name,
size,
mime_type,
hash,
},
reply_to: None,
metadata: MessageMetadata::default(),
}
}
pub fn reaction(sender: &str, recipient: &str, emoji: impl Into<String>, reply_to: &str) -> Self {
Self {
id: Self::generate_id(),
msg_type: MessageType::Reaction,
sender: sender.to_string(),
recipient: recipient.to_string(),
timestamp: Self::now(),
content: MessageContent::Reaction(emoji.into()),
reply_to: Some(reply_to.to_string()),
metadata: MessageMetadata::default(),
}
}
pub fn typing(sender: &str, recipient: &str) -> Self {
Self {
id: Self::generate_id(),
msg_type: MessageType::Typing,
sender: sender.to_string(),
recipient: recipient.to_string(),
timestamp: Self::now(),
content: MessageContent::Empty,
reply_to: None,
metadata: MessageMetadata::default(),
}
}
pub fn read(sender: &str, recipient: &str, last_read: &str) -> Self {
Self {
id: Self::generate_id(),
msg_type: MessageType::Read,
sender: sender.to_string(),
recipient: recipient.to_string(),
timestamp: Self::now(),
content: MessageContent::Empty,
reply_to: Some(last_read.to_string()),
metadata: MessageMetadata::default(),
}
}
pub fn with_reply(mut self, reply_to: &str) -> Self {
self.reply_to = Some(reply_to.to_string());
self
}
pub fn with_pqc(mut self) -> Self {
self.metadata.pqc_encrypted = true;
self
}
pub fn with_mention(mut self, fingerprint: &str) -> Self {
self.metadata.mentions.push(fingerprint.to_string());
self
}
pub fn encode(&self) -> Result<Vec<u8>> {
bincode::serialize(self)
.map_err(|e| Error::Serialization(e.to_string()))
}
pub fn decode(bytes: &[u8]) -> Result<Self> {
bincode::deserialize(bytes)
.map_err(|e| Error::Serialization(e.to_string()))
}
fn generate_id() -> String {
use rand::Rng;
let mut rng = rand::thread_rng();
(0..16)
.map(|_| format!("{:02x}", rng.gen::<u8>()))
.collect()
}
fn now() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as u64
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_text_message() {
let msg = Message::text("alice", "bob", "Hello!");
assert_eq!(msg.msg_type, MessageType::Text);
assert!(matches!(msg.content, MessageContent::Text(_)));
}
#[test]
fn test_message_encoding() {
let msg = Message::text("alice", "bob", "Test");
let encoded = msg.encode().unwrap();
let decoded = Message::decode(&encoded).unwrap();
assert_eq!(decoded.sender, "alice");
}
#[test]
fn test_message_type_conversion() {
assert_eq!(MessageType::try_from(0).unwrap(), MessageType::Text);
assert!(MessageType::try_from(255).is_err());
}
}