use std::collections::HashMap;
use enigma_packet::{Message, MessageMeta};
use uuid::Uuid;
use crate::crypto::{conversation_ad_bytes, SessionRatchet};
use crate::error::{EnigmaProtocolError, Result};
use crate::types::SessionBootstrap;
pub struct Session {
local_id: String,
remote_id: String,
ratchet: SessionRatchet,
attachments: HashMap<Uuid, AttachmentTracker>,
}
struct AttachmentTracker {
meta: enigma_packet::AttachmentMeta,
received_chunks: u32,
received_size: u64,
}
pub enum AttachmentUpdate {
Init {
attachment_id: Uuid,
total_chunks: u32,
},
Chunk {
attachment_id: Uuid,
received_chunks: u32,
total_chunks: u32,
},
End {
attachment_id: Uuid,
total_size: u64,
},
}
impl Session {
pub fn new(local: &str, remote: &str, bootstrap: &SessionBootstrap) -> Result<Self> {
let ratchet = SessionRatchet::from_bootstrap(bootstrap, local, remote)?;
Ok(Self {
local_id: local.to_string(),
remote_id: remote.to_string(),
ratchet,
attachments: HashMap::new(),
})
}
pub fn conversation_id(&self) -> Uuid {
self.ratchet.conversation_id()
}
pub fn local_id(&self) -> &str {
&self.local_id
}
pub fn remote_id(&self) -> &str {
&self.remote_id
}
pub fn encrypt_message(&mut self, message: &Message) -> Result<Vec<u8>> {
let encoded = enigma_packet::encode_message(message)?;
let key = self.ratchet.next_send_key()?;
let box_ = enigma_aead::AeadBox::new(key);
let ad = conversation_ad_bytes(&self.conversation_id(), &self.local_id, &self.remote_id);
let ciphertext = box_.encrypt(&encoded, &ad)?;
Ok(ciphertext)
}
pub fn decrypt_packet(&mut self, packet: &[u8]) -> Result<Message> {
let key = self.ratchet.next_recv_key()?;
let box_ = enigma_aead::AeadBox::new(key);
let ad = conversation_ad_bytes(&self.conversation_id(), &self.remote_id, &self.local_id);
let plaintext = box_.decrypt(packet, &ad)?;
let message = enigma_packet::decode_message(&plaintext)?;
Ok(message)
}
pub fn handle_attachment_message(
&mut self,
message: &Message,
) -> Result<Option<AttachmentUpdate>> {
match &message.meta {
MessageMeta::AttachmentInit(meta) => {
let tracker = AttachmentTracker {
meta: meta.clone(),
received_chunks: 0,
received_size: 0,
};
self.attachments.insert(meta.attachment_id, tracker);
Ok(Some(AttachmentUpdate::Init {
attachment_id: meta.attachment_id,
total_chunks: meta.chunk_count,
}))
}
MessageMeta::AttachmentChunk(chunk) => {
let tracker = self
.attachments
.get_mut(&chunk.attachment_id)
.ok_or(EnigmaProtocolError::Attachment)?;
if chunk.index != tracker.received_chunks {
return Err(EnigmaProtocolError::Attachment);
}
tracker.received_chunks = tracker
.received_chunks
.checked_add(1)
.ok_or(EnigmaProtocolError::Attachment)?;
tracker.received_size = tracker
.received_size
.checked_add(chunk.chunk_size as u64)
.ok_or(EnigmaProtocolError::Attachment)?;
Ok(Some(AttachmentUpdate::Chunk {
attachment_id: chunk.attachment_id,
received_chunks: tracker.received_chunks,
total_chunks: tracker.meta.chunk_count,
}))
}
MessageMeta::AttachmentEnd {
attachment_id,
total_size,
chunk_count,
..
} => {
let tracker = self
.attachments
.remove(attachment_id)
.ok_or(EnigmaProtocolError::Attachment)?;
if &tracker.meta.chunk_count != chunk_count {
return Err(EnigmaProtocolError::Attachment);
}
if &tracker.meta.total_size != total_size {
return Err(EnigmaProtocolError::Attachment);
}
Ok(Some(AttachmentUpdate::End {
attachment_id: *attachment_id,
total_size: *total_size,
}))
}
_ => Ok(None),
}
}
}