From ebc1e42d2c3fc3f76b577936162d738fb777dbc8 Mon Sep 17 00:00:00 2001 From: Jeff Baskin Date: Thu, 10 Apr 2025 23:56:16 -0400 Subject: [PATCH] Have sessions expire. --- src/clock.rs | 2 +- src/session.rs | 163 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 151 insertions(+), 14 deletions(-) diff --git a/src/clock.rs b/src/clock.rs index 0a1a493..d52b4e3 100644 --- a/src/clock.rs +++ b/src/clock.rs @@ -5,7 +5,7 @@ use std::{ time::Duration, }; -const SLEEP_FOR: Duration = Duration::from_millis(1000); +const SLEEP_FOR: Duration = Duration::from_secs(1); pub struct Clock { queue: Queue, diff --git a/src/session.rs b/src/session.rs index cc267d4..7ccb27d 100644 --- a/src/session.rs +++ b/src/session.rs @@ -2,16 +2,81 @@ use crate::{ field::Field, queue::{Message, MsgType, Queue}, }; +use chrono::prelude::*; use std::{ collections::HashMap, sync::mpsc::{channel, Receiver}, thread::spawn, + time::Duration, }; use uuid::Uuid; -const RESPONDS_TO: [MsgType; 1] = [MsgType::SessionValidate]; +const EXPIRE_IN: Duration = Duration::from_secs(60 * 60); +const RESPONDS_TO: [MsgType; 2] = [MsgType::SessionValidate, MsgType::Time]; -struct SessionData; +struct SessionData { + expire_on: DateTime, +} + +impl SessionData { + fn new() -> Self { + Self { + expire_on: Utc::now() + EXPIRE_IN, + } + } + + fn extend(&mut self) { + self.expire_on = Utc::now() + EXPIRE_IN; + } + + fn is_expired(&self, now: &DateTime) -> bool { + now > &self.expire_on + } +} + +#[cfg(test)] +mod sessiondatas { + use super::*; + + #[test] + fn create_session_data() { + let expire = Utc::now() + EXPIRE_IN; + let data = SessionData::new(); + assert!( + data.expire_on > expire, + "{:?} should be greater than {:?}", + data.expire_on, + expire + ); + } + + #[test] + fn extend_usage_time() { + let mut data = SessionData::new(); + let expire = Utc::now() + EXPIRE_IN; + data.extend(); + assert!( + data.expire_on > expire, + "{:?} should be greater than {:?}", + data.expire_on, + expire + ); + } + + #[test] + fn is_expired() { + let data = SessionData::new(); + let expire = Utc::now() + EXPIRE_IN; + assert!(data.is_expired(&expire), "should be expired"); + } + + #[test] + fn is_not_expired() { + let expire = Utc::now() + EXPIRE_IN; + let data = SessionData::new(); + assert!(!data.is_expired(&expire), "should be not expired"); + } +} pub struct Session { data: HashMap, @@ -40,22 +105,26 @@ impl Session { fn listen(&mut self) { loop { let msg = self.rx.recv().unwrap(); - self.validate(msg); + match msg.get_class() { + MsgType::SessionValidate => self.validate(msg), + MsgType::Time => self.expire(msg), + _ => unreachable!("received unknown message"), + }; } } fn validate(&mut self, msg: Message) { match msg.get_data("sess_id") { Some(sid) => match sid { - Field::Uuid(sess_id) => { - if self.data.contains_key(&sess_id) { + Field::Uuid(sess_id) => match self.data.get_mut(&sess_id) { + Some(sess_data) => { + sess_data.extend(); let mut reply = msg.reply(MsgType::Session); reply.add_data("sess_id", sess_id.clone()); self.queue.send(reply).unwrap(); - } else { - self.new_session(msg); } - } + None => self.new_session(msg), + }, _ => self.new_session(msg), }, None => self.new_session(msg), @@ -67,11 +136,24 @@ impl Session { while self.data.contains_key(&id) { id = Uuid::new_v4(); } - self.data.insert(id.clone(), SessionData {}); + self.data.insert(id.clone(), SessionData::new()); let mut reply = msg.reply(MsgType::Session); reply.add_data("sess_id", id); self.queue.send(reply).unwrap(); } + + fn expire(&mut self, msg: Message) { + let now = msg.get_data("time").unwrap().to_datetime().unwrap(); + let mut expired: Vec = Vec::new(); + for (id, data) in self.data.iter() { + if data.is_expired(&now) { + expired.push(id.clone()); + } + } + for id in expired.iter() { + self.data.remove(id); + } + } } #[cfg(test)] @@ -90,6 +172,13 @@ mod sessions { (queue, rx) } + fn create_session(queue: &Queue, rx: &Receiver) -> Uuid { + let msg = Message::new(MsgType::SessionValidate); + queue.send(msg.clone()).unwrap(); + let holder = rx.recv_timeout(TIMEOUT).unwrap(); + holder.get_data("sess_id").unwrap().to_uuid().unwrap() + } + #[test] fn get_new_session() { let listen_for = [MsgType::Session]; @@ -123,13 +212,11 @@ mod sessions { } #[test] - fn existing_id_are_returned() { + fn existing_id_is_returned() { let listen_for = [MsgType::Session]; let (queue, rx) = setup_session(listen_for.to_vec()); + let id = create_session(&queue, &rx); let mut msg = Message::new(MsgType::SessionValidate); - queue.send(msg.clone()).unwrap(); - let holder = rx.recv_timeout(TIMEOUT).unwrap(); - let id = holder.get_data("sess_id").unwrap().to_uuid().unwrap(); msg.add_data("sess_id", id.clone()); queue.send(msg).unwrap(); let result = rx.recv_timeout(TIMEOUT).unwrap(); @@ -162,4 +249,54 @@ mod sessions { let output = result.get_data("sess_id").unwrap().to_string(); assert_ne!(output, id); } + + #[test] + fn timer_does_nothing_to_unexpired() { + let expire = Utc::now() + EXPIRE_IN; + let listen_for = [MsgType::Session]; + let (queue, rx) = setup_session(listen_for.to_vec()); + let id = create_session(&queue, &rx); + let mut time_msg = Message::new(MsgType::Time); + time_msg.add_data("time", expire); + queue.send(time_msg).unwrap(); + let mut validate_msg = Message::new(MsgType::SessionValidate); + validate_msg.add_data("sess_id", id.clone()); + queue.send(validate_msg).unwrap(); + let result = rx.recv_timeout(TIMEOUT).unwrap(); + assert_eq!(result.get_data("sess_id").unwrap().to_uuid().unwrap(), id); + } + + #[test] + fn timer_removes_expired() { + let listen_for = [MsgType::Session]; + let (queue, rx) = setup_session(listen_for.to_vec()); + let id = create_session(&queue, &rx); + let expire = Utc::now() + EXPIRE_IN; + let mut time_msg = Message::new(MsgType::Time); + time_msg.add_data("time", expire); + queue.send(time_msg).unwrap(); + let mut validate_msg = Message::new(MsgType::SessionValidate); + validate_msg.add_data("sess_id", id.clone()); + queue.send(validate_msg).unwrap(); + let result = rx.recv_timeout(TIMEOUT).unwrap(); + assert_ne!(result.get_data("sess_id").unwrap().to_uuid().unwrap(), id); + } + + #[test] + fn validate_extends_session() { + let listen_for = [MsgType::Session]; + let (queue, rx) = setup_session(listen_for.to_vec()); + let id = create_session(&queue, &rx); + let mut validate_msg = Message::new(MsgType::SessionValidate); + validate_msg.add_data("sess_id", id.clone()); + let expire = Utc::now() + EXPIRE_IN; + let mut time_msg = Message::new(MsgType::Time); + time_msg.add_data("time", expire); + queue.send(validate_msg.clone()).unwrap(); + queue.send(time_msg).unwrap(); + queue.send(validate_msg).unwrap(); + rx.recv_timeout(TIMEOUT).unwrap(); + let result = rx.recv_timeout(TIMEOUT).unwrap(); + assert_eq!(result.get_data("sess_id").unwrap().to_uuid().unwrap(), id); + } }