diff --git a/src/cache/hashcache.rs b/src/cache/hashcache.rs index 62984b6..0272cad 100644 --- a/src/cache/hashcache.rs +++ b/src/cache/hashcache.rs @@ -23,34 +23,32 @@ use actix::prelude::*; use super::messages::*; use super::Save; use crate::errors::*; -use crate::pow::PoWConfig; #[derive(Clone, Default)] /// cache datastructure implementing [Save] pub struct HashCache(HashMap); impl HashCache { - fn save(&mut self, config: PoWConfig) -> CaptchaResult<()> { + // save [PoWConfig] to cache + fn save(&mut self, config: Cache) -> CaptchaResult<()> { self.0.insert(config.string, config.difficulty_factor); Ok(()) } + // retrive [PoWConfig] from cache. Deletes config post retrival fn retrive(&mut self, string: String) -> CaptchaResult> { - if let Some(difficulty_factor) = self.0.get(&string) { + if let Some(difficulty_factor) = self.remove(&string) { Ok(Some(difficulty_factor.to_owned())) } else { Ok(None) } } -} -/* TODO cache of pow configs need to have lifetimes to prevent replay attacks - * where lifetime = site's cool down period so that people can't generate pow - * configs when the site is cool and use them later with rainbow tables - * when it's under attack. - * - * This comment stays until this feature is implemented. - */ + // delete [PoWConfig] from cache + fn remove(&mut self, string: &str) -> Option { + self.0.remove(string) + } +} impl Save for HashCache {} @@ -61,8 +59,31 @@ impl Actor for HashCache { /// cache a PoWConfig impl Handler for HashCache { type Result = MessageResult; - fn handle(&mut self, msg: Cache, _ctx: &mut Self::Context) -> Self::Result { - MessageResult(self.save(msg.0)) + fn handle(&mut self, msg: Cache, ctx: &mut Self::Context) -> Self::Result { + use actix::clock::delay_for; + use std::time::Duration; + + let addr = ctx.address(); + let del_msg = DeleteString(msg.string.clone()); + + let duration: Duration = Duration::new(msg.duration.clone(), 0); + let wait_for = async move { + delay_for(duration).await; + addr.send(del_msg).await.unwrap().unwrap(); + } + .into_actor(self); + ctx.spawn(wait_for); + + MessageResult(self.save(msg)) + } +} + +/// Delte a PoWConfig +impl Handler for HashCache { + type Result = MessageResult; + fn handle(&mut self, msg: DeleteString, _ctx: &mut Self::Context) -> Self::Result { + self.remove(&msg.0); + MessageResult(Ok(())) } } @@ -77,14 +98,38 @@ impl Handler for HashCache { #[cfg(test)] mod tests { use super::*; + use crate::mcaptcha::VisitorResult; + use crate::pow::PoWConfig; + + async fn sleep(time: u64) { + use actix::clock::delay_for; + use std::time::Duration; + + let duration: Duration = Duration::new(time, 0); + delay_for(duration).await; + } #[actix_rt::test] async fn hashcache_works() { + const DIFFICULTY_FACTOR: u32 = 54; + const DURATION: u64 = 5; let addr = HashCache::default().start(); - let cache: PoWConfig = PoWConfig::new(54); - let string = cache.string.clone(); - addr.send(Cache(cache)).await.unwrap().unwrap(); - let difficulty_factor = addr.send(Retrive(string)).await.unwrap().unwrap(); - assert_eq!(difficulty_factor.unwrap(), 54); + let pow: PoWConfig = PoWConfig::new(DIFFICULTY_FACTOR); + let visitor_result = VisitorResult { + difficulty_factor: DIFFICULTY_FACTOR, + duration: DURATION, + }; + let string = pow.string.clone(); + let msg = Cache::new(&pow, &visitor_result); + + addr.send(msg).await.unwrap().unwrap(); + + let cache_difficulty_factor = addr.send(Retrive(string.clone())).await.unwrap().unwrap(); + assert_eq!(DIFFICULTY_FACTOR, cache_difficulty_factor.unwrap()); + + sleep(DURATION + DURATION).await; + + let expired_string = addr.send(Retrive(string)).await.unwrap().unwrap(); + assert_eq!(None, expired_string); } } diff --git a/src/cache/mod.rs b/src/cache/mod.rs index c877134..f3c0ff8 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -23,22 +23,48 @@ use messages::*; pub mod hashcache; /// Describes actor handler trait impls that are required by a cache implementation -pub trait Save: actix::Actor + actix::Handler + actix::Handler {} - +pub trait Save: + actix::Actor + actix::Handler + actix::Handler + actix::Handler +{ +} pub mod messages { //! Messages that can be sent to cache data structures implementing [Save][super::Save] - use crate::pow::PoWConfig; use actix::dev::*; + use derive_builder::Builder; use crate::errors::*; + use crate::mcaptcha::VisitorResult; + use crate::pow::PoWConfig; - /// Message to decrement the visitor count - #[derive(Message)] + /// Message to cache PoW difficulty factor and string + #[derive(Message, Builder)] #[rtype(result = "CaptchaResult<()>")] - pub struct Cache(pub PoWConfig); + pub struct Cache { + pub string: String, + pub difficulty_factor: u32, + pub duration: u64, + } - /// Message to decrement the visitor count + impl Cache { + pub fn new(p: &PoWConfig, v: &VisitorResult) -> Self { + CacheBuilder::default() + .string(p.string.clone()) + .difficulty_factor(v.difficulty_factor) + .duration(v.duration) + .build() + .unwrap() + } + } + + /// Message to retrive the the difficulty factor for the specified + /// string from the cache #[derive(Message)] #[rtype(result = "CaptchaResult>")] pub struct Retrive(pub String); + + /// Message to delete cached PoW difficulty factor and string + /// when they expire + #[derive(Message)] + #[rtype(result = "CaptchaResult<()>")] + pub struct DeleteString(pub String); } diff --git a/src/data.rs b/src/data.rs index ba47bcd..d887f0b 100644 --- a/src/data.rs +++ b/src/data.rs @@ -49,13 +49,11 @@ where if site_addr.is_none() { return None; } - let difficulty_factor = site_addr.unwrap().send(Visitor).await.unwrap(); - let pow_config = PoWConfig::new(difficulty_factor); - self.cache - .send(Cache(pow_config.clone())) - .await - .unwrap() - .unwrap(); + let mcaptcha = site_addr.unwrap().send(Visitor).await.unwrap(); + let pow_config = PoWConfig::new(mcaptcha.difficulty_factor); + + let cache_msg = Cache::new(&pow_config, &mcaptcha); + self.cache.send(cache_msg).await.unwrap().unwrap(); Some(pow_config) } @@ -138,14 +136,6 @@ mod tests { let work = config .prove_work(&work_req.string, work_req.difficulty_factor) .unwrap(); - - let insufficient_work = config.prove_work(&work_req.string, 1).unwrap(); - let insufficient_work_payload = Work { - string: work_req.string.clone(), - result: insufficient_work.result, - nonce: insufficient_work.nonce, - }; - let mut payload = Work { string: work_req.string, result: work.result, @@ -159,8 +149,14 @@ mod tests { let res = actors.verify_pow(payload.clone()).await; assert_eq!(res, Err(CaptchaError::StringNotFound)); + let insufficient_work_req = actors.get_pow(MCAPTCHA_NAME.into()).await.unwrap(); + let insufficient_work = config.prove_work(&insufficient_work_req.string, 1).unwrap(); + let insufficient_work_payload = Work { + string: insufficient_work_req.string, + result: insufficient_work.result, + nonce: insufficient_work.nonce, + }; let res = actors.verify_pow(insufficient_work_payload.clone()).await; - assert_eq!(res, Err(CaptchaError::InsuffiencientDifficulty)); } } diff --git a/src/mcaptcha.rs b/src/mcaptcha.rs index 7b41788..b7aca52 100644 --- a/src/mcaptcha.rs +++ b/src/mcaptcha.rs @@ -111,6 +111,11 @@ impl MCaptcha { pub fn get_difficulty(&self) -> u32 { self.defense.get_difficulty() } + + /// get [MCaptcha]'s lifetime + pub fn get_duration(&self) -> u64 { + self.duration + } } impl Actor for MCaptcha { type Context = Context; @@ -129,12 +134,30 @@ impl Handler for MCaptcha { } /// Message to increment the visitor count +/// returns difficulty factor and lifetime #[derive(Message)] -#[rtype(result = "u32")] +#[rtype(result = "VisitorResult")] pub struct Visitor; +/// Struct representing the return datatime of +/// [Visitor] message. Contains MCaptcha lifetime +/// and difficulty factor +pub struct VisitorResult { + pub duration: u64, + pub difficulty_factor: u32, +} + +impl VisitorResult { + fn new(m: &MCaptcha) -> Self { + VisitorResult { + duration: m.get_duration(), + difficulty_factor: m.get_difficulty(), + } + } +} + impl Handler for MCaptcha { - type Result = u32; + type Result = MessageResult; fn handle(&mut self, _: Visitor, ctx: &mut Self::Context) -> Self::Result { use actix::clock::delay_for; @@ -150,7 +173,7 @@ impl Handler for MCaptcha { ctx.spawn(wait_for); self.add_visitor(); - self.get_difficulty() + MessageResult(VisitorResult::new(&self)) } } @@ -209,12 +232,12 @@ pub mod tests { async fn counter_defense_tightenup_works() { let addr: MyActor = get_counter().start(); - let mut difficulty_factor = addr.send(Visitor).await.unwrap(); - assert_eq!(difficulty_factor, LEVEL_1.0); + let mut mcaptcha = addr.send(Visitor).await.unwrap(); + assert_eq!(mcaptcha.difficulty_factor, LEVEL_1.0); race(addr.clone(), LEVEL_2).await; - difficulty_factor = addr.send(Visitor).await.unwrap(); - assert_eq!(difficulty_factor, LEVEL_2.1); + mcaptcha = addr.send(Visitor).await.unwrap(); + assert_eq!(mcaptcha.difficulty_factor, LEVEL_2.1); } #[actix_rt::test] @@ -224,13 +247,13 @@ pub mod tests { race(addr.clone(), LEVEL_2).await; race(addr.clone(), LEVEL_2).await; - let mut difficulty_factor = addr.send(Visitor).await.unwrap(); - assert_eq!(difficulty_factor, LEVEL_2.1); + let mut mcaptcha = addr.send(Visitor).await.unwrap(); + assert_eq!(mcaptcha.difficulty_factor, LEVEL_2.1); let duration = Duration::new(DURATION, 0); delay_for(duration).await; - difficulty_factor = addr.send(Visitor).await.unwrap(); - assert_eq!(difficulty_factor, LEVEL_1.1); + mcaptcha = addr.send(Visitor).await.unwrap(); + assert_eq!(mcaptcha.difficulty_factor, LEVEL_1.1); } }