use crate::error::{EnigmaPacketError, Result};
use crate::types::{AttachmentKind, AttachmentMeta, Message, MessageMeta, MessageType};
pub const MAX_PARTICIPANT_LEN: usize = 64;
pub fn validate_message(message: &Message) -> Result<()> {
validate_participants(message)?;
if message.timestamp_ms == 0 {
return Err(EnigmaPacketError::ValidationError(
"timestamp must be positive".into(),
));
}
match message.msg_type {
MessageType::Text => validate_text_message(message),
MessageType::Voice
| MessageType::Image
| MessageType::Video
| MessageType::File
| MessageType::CallOffer
| MessageType::CallAnswer
| MessageType::CallHangup
| MessageType::GroupInvite => validate_basic_payload(message),
MessageType::AttachmentInit => validate_attachment_init(message),
MessageType::AttachmentChunk => validate_attachment_chunk(message),
MessageType::AttachmentEnd => validate_attachment_end(message),
MessageType::AttachmentAbort => validate_attachment_abort(message),
MessageType::AttachmentAck => validate_attachment_ack(message),
}
}
fn validate_participants(message: &Message) -> Result<()> {
if message.sender.is_empty() || message.sender.chars().count() > MAX_PARTICIPANT_LEN {
return Err(EnigmaPacketError::ValidationError("invalid sender".into()));
}
if message.receiver.is_empty() || message.receiver.chars().count() > MAX_PARTICIPANT_LEN {
return Err(EnigmaPacketError::ValidationError(
"invalid receiver".into(),
));
}
Ok(())
}
fn validate_text_message(message: &Message) -> Result<()> {
if std::str::from_utf8(&message.payload).is_err() {
return Err(EnigmaPacketError::ValidationError(
"text payload must be utf-8".into(),
));
}
match &message.meta {
MessageMeta::None | MessageMeta::Basic { .. } => Ok(()),
_ => Err(EnigmaPacketError::ValidationError(
"invalid metadata for text".into(),
)),
}
}
fn validate_basic_payload(message: &Message) -> Result<()> {
match &message.meta {
MessageMeta::None | MessageMeta::Basic { .. } => Ok(()),
_ => Err(EnigmaPacketError::ValidationError(
"invalid metadata for inline message".into(),
)),
}
}
fn validate_attachment_init(message: &Message) -> Result<()> {
if !message.payload.is_empty() {
return Err(EnigmaPacketError::ValidationError(
"attachment init payload must be empty".into(),
));
}
let meta = match &message.meta {
MessageMeta::AttachmentInit(meta) => meta,
_ => {
return Err(EnigmaPacketError::ValidationError(
"attachment init metadata missing".into(),
))
}
};
validate_attachment_meta(meta)
}
fn validate_attachment_meta(meta: &AttachmentMeta) -> Result<()> {
if meta.total_size == 0 {
return Err(EnigmaPacketError::ValidationError(
"total size must be positive".into(),
));
}
if meta.chunk_size == 0 {
return Err(EnigmaPacketError::ValidationError(
"chunk size must be positive".into(),
));
}
if meta.chunk_count == 0 {
return Err(EnigmaPacketError::ValidationError(
"chunk count must be positive".into(),
));
}
if let Some(created) = meta.created_at_ms {
if created == 0 {
return Err(EnigmaPacketError::ValidationError(
"created_at must be positive".into(),
));
}
}
if matches!(meta.kind, AttachmentKind::File) {
match &meta.filename {
Some(name) if !name.is_empty() => {}
_ => {
return Err(EnigmaPacketError::ValidationError(
"filename required for file attachments".into(),
))
}
}
}
let chunk_size = meta.chunk_size as u64;
let expected = ceil_div(meta.total_size, chunk_size);
if expected != meta.chunk_count as u64 {
return Err(EnigmaPacketError::ValidationError(
"chunk count mismatch".into(),
));
}
Ok(())
}
fn ceil_div(total: u64, chunk: u64) -> u64 {
let mut count = total / chunk;
if total % chunk != 0 {
count += 1;
}
count
}
fn validate_attachment_chunk(message: &Message) -> Result<()> {
if message.payload.is_empty() {
return Err(EnigmaPacketError::ValidationError(
"attachment chunk payload must not be empty".into(),
));
}
let meta = match &message.meta {
MessageMeta::AttachmentChunk(meta) => meta,
_ => {
return Err(EnigmaPacketError::ValidationError(
"attachment chunk metadata missing".into(),
))
}
};
if meta.chunk_size == 0 {
return Err(EnigmaPacketError::ValidationError(
"chunk size must be positive".into(),
));
}
let chunk_size = meta.chunk_size as u64;
let expected_offset = (meta.index as u64)
.checked_mul(chunk_size)
.ok_or_else(|| EnigmaPacketError::ValidationError("chunk offset overflow".into()))?;
if meta.offset != expected_offset {
return Err(EnigmaPacketError::ValidationError(
"chunk offset mismatch".into(),
));
}
if message.payload.len() as u64 > chunk_size {
return Err(EnigmaPacketError::ValidationError(
"chunk payload larger than declared chunk size".into(),
));
}
if let Some(total) = meta.total_size {
if total == 0 {
return Err(EnigmaPacketError::ValidationError(
"chunk total size must be positive".into(),
));
}
if meta.offset > total {
return Err(EnigmaPacketError::ValidationError(
"chunk offset beyond total size".into(),
));
}
let end = meta
.offset
.checked_add(message.payload.len() as u64)
.ok_or_else(|| EnigmaPacketError::ValidationError("chunk exceeds total size".into()))?;
if end > total {
return Err(EnigmaPacketError::ValidationError(
"chunk exceeds total size".into(),
));
}
}
Ok(())
}
fn validate_attachment_end(message: &Message) -> Result<()> {
if !message.payload.is_empty() {
return Err(EnigmaPacketError::ValidationError(
"attachment end payload must be empty".into(),
));
}
match &message.meta {
MessageMeta::AttachmentEnd {
attachment_id: _,
sha256: _,
total_size,
chunk_count,
} => {
if *total_size == 0 || *chunk_count == 0 {
return Err(EnigmaPacketError::ValidationError(
"attachment end totals must be positive".into(),
));
}
Ok(())
}
_ => Err(EnigmaPacketError::ValidationError(
"attachment end metadata missing".into(),
)),
}
}
fn validate_attachment_abort(message: &Message) -> Result<()> {
if !message.payload.is_empty() {
return Err(EnigmaPacketError::ValidationError(
"attachment abort payload must be empty".into(),
));
}
match &message.meta {
MessageMeta::AttachmentAbort { .. } => Ok(()),
_ => Err(EnigmaPacketError::ValidationError(
"attachment abort metadata missing".into(),
)),
}
}
fn validate_attachment_ack(message: &Message) -> Result<()> {
if !message.payload.is_empty() {
return Err(EnigmaPacketError::ValidationError(
"attachment ack payload must be empty".into(),
));
}
match &message.meta {
MessageMeta::AttachmentAck(_) => Ok(()),
_ => Err(EnigmaPacketError::ValidationError(
"attachment ack metadata missing".into(),
)),
}
}