From 8ddaf4ec2e5145a3a8b899f41dce872f7d939b17 Mon Sep 17 00:00:00 2001 From: realaravinth Date: Fri, 5 Mar 2021 21:52:41 +0530 Subject: [PATCH] in-memory pow config cache --- Cargo.lock | 11 +- Cargo.toml | 10 +- src/{counter.rs => config.rs} | 193 ++++++++++++++++++++++++++++------ src/errors.rs | 4 + src/hashcache.rs | 92 ++++++++++++++++ src/lib.rs | 17 ++- 6 files changed, 287 insertions(+), 40 deletions(-) rename src/{counter.rs => config.rs} (54%) create mode 100644 src/hashcache.rs diff --git a/Cargo.lock b/Cargo.lock index 51164f3..bce7f60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,9 +104,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.42" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d" +checksum = "3a4c64e223db1fffa7683a719921434caa880463cfa5820032b063c9ecd5cc49" dependencies = [ "proc-macro2", "quote", @@ -684,9 +684,12 @@ version = "0.1.0" dependencies = [ "actix", "actix-rt", + "async-trait", "derive_builder", "derive_more", "lazy_static", + "pow_sha256", + "rand", "serde", "serde_json", ] @@ -1145,9 +1148,9 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" [[package]] name = "syn" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" +checksum = "ed22b90a0e734a23a7610f4283ac9e5acfb96cbb30dfefa540d66f866f1c09c5" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 9597477..0198d74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,9 +16,17 @@ members = [ ".", "browser", "cli" ] [dependencies] actix = "0.10" +actix-rt = "1" + serde = "1.0.114" serde_json = "1" + derive_builder = "0.9" derive_more = "0.99" + lazy_static = "1.4" -actix-rt = "1" + +rand = "0.7" +pow_sha256 = "0.2.1" + +async-trait = "0.1.44" diff --git a/src/counter.rs b/src/config.rs similarity index 54% rename from src/counter.rs rename to src/config.rs index d0a10e2..c9b83d6 100644 --- a/src/counter.rs +++ b/src/config.rs @@ -19,7 +19,7 @@ //! //! ## Usage: //! ```rust -//! use m_captcha::{message::Visitor,MCaptchaBuilder, LevelBuilder, DefenseBuilder}; +//! use m_captcha::{message::Visitor, MCaptchaBuilder, cache::HashCache, LevelBuilder, DefenseBuilder}; //! // traits from actix needs to be in scope for starting actor //! use actix::prelude::*; //! @@ -56,10 +56,12 @@ //! .unwrap(); //! //! // create and start MCaptcha actor +//! let cache = HashCache::default().start(); //! let mcaptcha = MCaptchaBuilder::default() //! .defense(defense) //! // leaky bucket algorithm's emission interval //! .duration(30) +//! .cache(cache) //! .build() //! .unwrap() //! .start(); @@ -71,34 +73,68 @@ //! } //! ``` +use std::collections::HashMap; +use std::sync::Arc; use std::time::Duration; +use actix::dev::*; use actix::prelude::*; +use async_trait::async_trait; use derive_builder::Builder; +use pow_sha256::PoW as ShaPoW; +use rand::{distributions::Alphanumeric, thread_rng, Rng}; +use serde::{Deserialize, Serialize}; use crate::defense::Defense; +use crate::errors::*; +use crate::hashcache::*; -/// Message to increment the visitor count -#[derive(Message)] -#[rtype(result = "u32")] -pub struct Visitor; +//#[async_trait] +//pub trait PersistPow { +// async fn save(&mut self, config: Arc) -> CaptchaResult<()>; +// async fn retrive(&mut self, string: &str) -> CaptchaResult>; +//} -/// Message to decrement the visitor count -#[derive(Message)] -#[rtype(result = "()")] -struct DeleteVisitor; +pub trait Save: actix::Actor + actix::Handler + actix::Handler {} /// This struct represents the mCaptcha state and is used /// to configure leaky-bucket lifetime and manage defense -#[derive(Builder)] -pub struct MCaptcha { +#[derive(Clone, Builder)] +pub struct MCaptcha +where + // Actor + Handler, + T: Save, + ::Context: ToEnvelope + ToEnvelope, + // ::Context: ToEnvelope, +{ #[builder(default = "0", setter(skip))] visitor_threshold: u32, defense: Defense, duration: u64, + cache: Addr, } -impl MCaptcha { +//#[async_trait] +//impl PersistPow for HashCache { +// async fn save(&mut self, config: Arc) -> CaptchaResult<()> { +// self.insert(config.string.clone(), config.difficulty_factor); +// Ok(()) +// } +// +// async fn retrive(&mut self, string: &str) -> CaptchaResult> { +// if let Some(difficulty_factor) = self.get(string) { +// Ok(Some(difficulty_factor.to_owned())) +// } else { +// Ok(None) +// } +// } +//} + +impl MCaptcha +where + T: Save, + ::Context: ToEnvelope + ToEnvelope, +{ /// incerment visiotr count by one pub fn add_visitor(&mut self) { self.visitor_threshold += 1; @@ -120,16 +156,79 @@ impl MCaptcha { pub fn get_difficulty(&self) -> u32 { self.defense.get_difficulty() } -} -impl Actor for MCaptcha { + // /// cache PoW configuration: difficulty and string + // pub async fn cache_pow(&mut self, pow: Arc) -> CaptchaResult<()> { + // unimplemented!(); + // Ok(self.cache.save(pow).await?) + // } + // + // /// retrivee PoW configuration: difficulty and string + // pub async fn retrive_pow(&mut self, pow: &PoWConfig) -> CaptchaResult> { + // unimplemented!(); + // Ok(self.cache.retrive(&pow.string).await?) + // } +} +impl Actor for MCaptcha +where + T: Save, + ::Context: ToEnvelope + ToEnvelope, +{ type Context = Context; } -impl Handler for MCaptcha { - type Result = u32; +/// Message to decrement the visitor count +#[derive(Message)] +#[rtype(result = "()")] +struct DeleteVisitor; + +impl Handler for MCaptcha +where + T: Save, + ::Context: ToEnvelope + ToEnvelope, +{ + type Result = (); + fn handle(&mut self, _msg: DeleteVisitor, _ctx: &mut Self::Context) -> Self::Result { + self.decrement_visiotr(); + } +} + +/// PoW Config that will be sent to clients for generating PoW +#[derive(Clone, Serialize, Debug)] +pub struct PoWConfig { + pub string: String, + pub difficulty_factor: u32, +} + +impl PoWConfig { + pub fn new(m: &MCaptcha) -> Self + where + T: Save, + ::Context: ToEnvelope + ToEnvelope, + { + PoWConfig { + string: thread_rng().sample_iter(&Alphanumeric).take(32).collect(), + difficulty_factor: m.get_difficulty(), + } + } +} + +/// Message to increment the visitor count +#[derive(Message)] +#[rtype(result = "CaptchaResult")] +//#[rtype(result = "()")] +pub struct Visitor; + +impl Handler for MCaptcha +where + T: Save, + ::Context: ToEnvelope + ToEnvelope, +{ + type Result = ResponseActFuture>; fn handle(&mut self, _: Visitor, ctx: &mut Self::Context) -> Self::Result { + use crate::hashcache::Cache; use actix::clock::delay_for; + use actix::fut::wrap_future; let addr = ctx.address(); @@ -142,13 +241,34 @@ impl Handler for MCaptcha { ctx.spawn(wait_for); self.add_visitor(); - self.get_difficulty() + let res = Arc::new(PoWConfig::new(&self)); + + let act_fut = wrap_future::<_, Self>(self.cache.send(Cache(res.clone()))).map( + |result, _actor, _ctx| match result { + Ok(Ok(())) => Ok(Arc::try_unwrap(res).unwrap()), + Ok(Err(e)) => Err(e), + Err(_) => Err(CaptchaError::MailboxError), //TODO do typecasting from mailbox error to captcha error + }, + ); + Box::pin(act_fut) } } -impl Handler for MCaptcha { +/// Message to decrement the visitor count +#[derive(Message, Deserialize)] +#[rtype(result = "()")] +pub struct VerifyPoW { + pow: ShaPoW>, + id: String, +} + +impl Handler for MCaptcha +where + T: Save, + ::Context: ToEnvelope + ToEnvelope, +{ type Result = (); - fn handle(&mut self, _msg: DeleteVisitor, _ctx: &mut Self::Context) -> Self::Result { + fn handle(&mut self, msg: VerifyPoW, _ctx: &mut Self::Context) -> Self::Result { self.decrement_visiotr(); } } @@ -164,6 +284,9 @@ mod tests { const LEVEL_2: (u32, u32) = (500, 500); const DURATION: u64 = 10; + type MyActor = Addr>; + type CacheAddr = Addr; + fn get_defense() -> Defense { DefenseBuilder::default() .add_level( @@ -188,15 +311,23 @@ mod tests { .unwrap() } - async fn race(addr: Addr, count: (u32, u32)) { + async fn race(addr: Addr>, count: (u32, u32)) + where + // Actor + Handler, + T: Save, + ::Context: ToEnvelope + ToEnvelope, + { for _ in 0..count.0 as usize - 1 { let _ = addr.send(Visitor).await.unwrap(); } } - fn get_counter() -> MCaptcha { + fn get_counter() -> MCaptcha { + use actix::prelude::*; + let cache: CacheAddr = HashCache::default().start(); MCaptchaBuilder::default() .defense(get_defense()) + .cache(cache) .duration(DURATION) .build() .unwrap() @@ -204,30 +335,30 @@ mod tests { #[actix_rt::test] async fn counter_defense_tightenup_works() { - let addr = get_counter().start(); + let addr: MyActor = get_counter().start(); - let mut difficulty_factor = addr.send(Visitor).await.unwrap(); - assert_eq!(difficulty_factor, LEVEL_1.0); + let mut difficulty_factor = addr.send(Visitor).await.unwrap().unwrap(); + assert_eq!(difficulty_factor.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); + difficulty_factor = addr.send(Visitor).await.unwrap().unwrap(); + assert_eq!(difficulty_factor.difficulty_factor, LEVEL_2.1); } #[actix_rt::test] async fn counter_defense_loosenup_works() { use actix::clock::delay_for; - let addr = get_counter().start(); + let addr: MyActor = get_counter().start(); 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 difficulty_factor = addr.send(Visitor).await.unwrap().unwrap(); + assert_eq!(difficulty_factor.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); + difficulty_factor = addr.send(Visitor).await.unwrap().unwrap(); + assert_eq!(difficulty_factor.difficulty_factor, LEVEL_1.1); } } diff --git a/src/errors.rs b/src/errors.rs index 0ef2d52..49a0602 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -49,6 +49,10 @@ pub enum CaptchaError { /// Difficulty factor should increase with level #[display(fmt = "Difficulty factor should increase with level")] DecreaseingDifficultyFactor, + + /// Difficulty factor should increase with level + #[display(fmt = "Actor mailbox error")] + MailboxError, } /// [Result] datatype for m_captcha diff --git a/src/hashcache.rs b/src/hashcache.rs new file mode 100644 index 0000000..a2c1675 --- /dev/null +++ b/src/hashcache.rs @@ -0,0 +1,92 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use actix::prelude::*; +use async_trait::async_trait; + +use crate::config::PoWConfig; +use crate::config::Save; +use crate::errors::*; + +#[derive(Clone, Default)] +pub struct HashCache(HashMap); + +impl HashCache { + fn save(&mut self, config: Arc) -> CaptchaResult<()> { + self.0 + .insert(config.string.clone(), config.difficulty_factor); + Ok(()) + } + + fn retrive(&mut self, string: Arc) -> CaptchaResult> { + if let Some(difficulty_factor) = self.0.get(&*string) { + Ok(Some(difficulty_factor.to_owned())) + } else { + Ok(None) + } + } +} + +impl Save for HashCache {} + +impl Actor for HashCache { + type Context = Context; +} + +/// Message to decrement the visitor count +#[derive(Message)] +#[rtype(result = "CaptchaResult<()>")] +pub struct Cache(pub Arc); + +impl Handler for HashCache { + type Result = MessageResult; + fn handle(&mut self, msg: Cache, _ctx: &mut Self::Context) -> Self::Result { + // if let Err(e) = self.save(msg.0.clone()) { + // MessageResult(Err(e)) + // } else { + // MessageResult(Ok(msg.0)) + // } + MessageResult(self.save(msg.0)) + } +} + +/// Message to decrement the visitor count +#[derive(Message)] +#[rtype(result = "CaptchaResult>")] +pub struct Retrive(pub Arc); + +impl Handler for HashCache { + type Result = MessageResult; + fn handle(&mut self, msg: Retrive, _ctx: &mut Self::Context) -> Self::Result { + MessageResult(self.retrive(msg.0.clone())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[actix_rt::test] + async fn counter_defense_tightenup_works() { + let addr = HashCache::default().start(); + let p = Arc::new("ewerw".to_string()); + addr.send(Retrive(p)).await.unwrap(); + } + // + // #[actix_rt::test] + // async fn counter_defense_loosenup_works() { + // use actix::clock::delay_for; + // let addr: MyActor = get_counter().start(); + // + // 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.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.difficulty_factor, LEVEL_1.1); + // } +} diff --git a/src/lib.rs b/src/lib.rs index 78a832f..e9fadd1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,7 +38,7 @@ //! ## Example: //! //! ```rust -//! use m_captcha::{LevelBuilder, DefenseBuilder, message::Visitor, MCaptchaBuilder}; +//! use m_captcha::{LevelBuilder, cache::HashCache, DefenseBuilder, message::Visitor, MCaptchaBuilder}; //! // traits from actix needs to be in scope for starting actor //! use actix::prelude::*; //! @@ -74,11 +74,14 @@ //! .build() //! .unwrap(); //! +//! let cache = HashCache::default().start(); +//! //! // create and start MCaptcha actor //! let mcaptcha = MCaptchaBuilder::default() //! .defense(defense) //! // leaky bucket algorithm's emission interval //! .duration(30) +//! .cache(cache) //! .build() //! .unwrap() //! .start(); @@ -90,14 +93,20 @@ //! } //! ``` -pub mod counter; +pub mod config; pub mod defense; pub mod errors; +pub mod hashcache; /// message datatypes to interact with [MCaptcha] actor pub mod message { - pub use crate::counter::Visitor; + pub use crate::config::Visitor; } -pub use counter::{MCaptcha, MCaptchaBuilder}; +/// message datatypes to interact with [MCaptcha] actor +pub mod cache { + pub use crate::hashcache::HashCache; +} + +pub use config::{MCaptcha, MCaptchaBuilder}; pub use defense::{Defense, DefenseBuilder, LevelBuilder};