use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use tokio::sync::RwLock;
use serde::{Serialize, Deserialize};
use crate::{QsslError, QsslResult};
use crate::crypto::{CipherSuite, symmetric};
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct SessionId(Vec<u8>);
impl SessionId {
pub fn new() -> Self {
let mut id = vec![0u8; 32];
rand::Rng::fill(&mut rand::thread_rng(), &mut id[..]);
Self(id)
}
pub fn from_bytes(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QsslSession {
pub id: Vec<u8>,
pub cipher_suite: CipherSuite,
pub master_secret: Vec<u8>,
pub client_random: [u8; 32],
pub server_random: [u8; 32],
pub created_at: SystemTime,
pub last_accessed: SystemTime,
pub ticket: Option<Vec<u8>>,
pub early_secret: Option<Vec<u8>>,
pub max_early_data: usize,
pub alpn_protocol: Option<String>,
pub server_name: Option<String>,
}
impl QsslSession {
pub fn new(
id: SessionId,
cipher_suite: CipherSuite,
master_secret: Vec<u8>,
client_random: [u8; 32],
server_random: [u8; 32],
) -> Self {
let now = SystemTime::now();
Self {
id: id.0,
cipher_suite,
master_secret,
client_random,
server_random,
created_at: now,
last_accessed: now,
ticket: None,
early_secret: None,
max_early_data: 0,
alpn_protocol: None,
server_name: None,
}
}
pub fn is_expired(&self, lifetime: Duration) -> bool {
self.created_at
.elapsed()
.map(|elapsed| elapsed > lifetime)
.unwrap_or(true)
}
pub fn touch(&mut self) {
self.last_accessed = SystemTime::now();
}
pub fn create_ticket(&mut self, key: &symmetric::SymmetricKey) -> QsslResult<Vec<u8>> {
let plaintext = bincode::serialize(self)
.map_err(|e| QsslError::Protocol(format!("Serialization failed: {}", e)))?;
let (ciphertext, nonce) = symmetric::encrypt(key, &plaintext, None)?;
let mut ticket = Vec::new();
ticket.extend_from_slice(&nonce);
ticket.extend_from_slice(&ciphertext);
self.ticket = Some(ticket.clone());
Ok(ticket)
}
pub fn from_ticket(ticket: &[u8], key: &symmetric::SymmetricKey) -> QsslResult<Self> {
if ticket.len() < 12 {
return Err(QsslError::Protocol("Invalid ticket".to_string()));
}
let nonce = &ticket[..12];
let ciphertext = &ticket[12..];
let plaintext = symmetric::decrypt(key, ciphertext, nonce, None)?;
bincode::deserialize(&plaintext)
.map_err(|e| QsslError::Protocol(format!("Deserialization failed: {}", e)))
}
}
pub struct SessionCache {
sessions: Arc<RwLock<HashMap<SessionId, QsslSession>>>,
max_size: usize,
lifetime: Duration,
}
impl SessionCache {
pub fn new(max_size: usize, lifetime: Duration) -> Self {
Self {
sessions: Arc::new(RwLock::new(HashMap::new())),
max_size,
lifetime,
}
}
pub async fn store(&self, session: QsslSession) -> QsslResult<()> {
let id = SessionId::from_bytes(session.id.clone());
let mut sessions = self.sessions.write().await;
sessions.retain(|_, s| !s.is_expired(self.lifetime));
if sessions.len() >= self.max_size {
if let Some(oldest_id) = sessions
.iter()
.min_by_key(|(_, s)| s.last_accessed)
.map(|(id, _)| id.clone())
{
sessions.remove(&oldest_id);
}
}
sessions.insert(id, session);
Ok(())
}
pub async fn get(&self, id: &SessionId) -> Option<QsslSession> {
let mut sessions = self.sessions.write().await;
if let Some(session) = sessions.get_mut(id) {
if !session.is_expired(self.lifetime) {
session.touch();
return Some(session.clone());
} else {
sessions.remove(id);
}
}
None
}
pub async fn remove(&self, id: &SessionId) -> bool {
self.sessions.write().await.remove(id).is_some()
}
pub async fn clear(&self) {
self.sessions.write().await.clear();
}
pub async fn size(&self) -> usize {
self.sessions.read().await.len()
}
pub async fn cleanup(&self) {
let mut sessions = self.sessions.write().await;
sessions.retain(|_, s| !s.is_expired(self.lifetime));
}
}
pub struct ClientSessionStore {
sessions: Arc<RwLock<HashMap<String, QsslSession>>>, }
impl ClientSessionStore {
pub fn new() -> Self {
Self {
sessions: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn store(&self, server: String, session: QsslSession) {
self.sessions.write().await.insert(server, session);
}
pub async fn get(&self, server: &str) -> Option<QsslSession> {
self.sessions.read().await.get(server).cloned()
}
pub async fn remove(&self, server: &str) -> bool {
self.sessions.write().await.remove(server).is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_session_id() {
let id1 = SessionId::new();
let id2 = SessionId::new();
assert_ne!(id1, id2);
assert_eq!(id1.as_bytes().len(), 32);
}
#[tokio::test]
async fn test_session_cache() {
let cache = SessionCache::new(10, Duration::from_secs(3600));
let id = SessionId::new();
let session = QsslSession::new(
id.clone(),
CipherSuite::Kyber768Falcon512Aes256,
vec![0; 48],
[0; 32],
[1; 32],
);
cache.store(session.clone()).await.unwrap();
assert_eq!(cache.size().await, 1);
let retrieved = cache.get(&id).await.unwrap();
assert_eq!(retrieved.id, session.id);
cache.remove(&id).await;
assert_eq!(cache.size().await, 0);
}
#[test]
fn test_session_ticket() {
let session = QsslSession::new(
SessionId::new(),
CipherSuite::Kyber768Falcon512Aes256,
vec![0; 48],
[0; 32],
[1; 32],
);
let key = symmetric::SymmetricKey::generate(crate::crypto::SymmetricCipher::Aes256Gcm);
let mut session_mut = session.clone();
let ticket = session_mut.create_ticket(&key).unwrap();
let decrypted = QsslSession::from_ticket(&ticket, &key).unwrap();
assert_eq!(decrypted.id, session.id);
assert_eq!(decrypted.master_secret, session.master_secret);
}
}