From cc88c69fc69c9c0321eeb3213a2690ca9469d38c Mon Sep 17 00:00:00 2001 From: realaravinth Date: Thu, 3 Jun 2021 19:40:45 +0530 Subject: [PATCH] moved mcaptcha into embedded --- CHANGELOG.md | 11 +- examples/simple.rs | 8 +- src/lib.rs | 11 +- .../embedded/{mcaptcha.rs => counter.rs} | 115 +++++++----------- src/master/embedded/master.rs | 59 ++++----- src/master/embedded/mod.rs | 2 +- src/master/mod.rs | 73 ++++++++++- src/system.rs | 11 +- 8 files changed, 168 insertions(+), 122 deletions(-) rename src/master/embedded/{mcaptcha.rs => counter.rs} (80%) diff --git a/CHANGELOG.md b/CHANGELOG.md index e727600..9146960 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 0.1.4 +## Added: + +- `Master` trait: provides methods to manage mcaptcha + ## Changed: - `PoWConfig` has an extra field to send internal `PoW` salt to clients. @@ -11,9 +15,14 @@ - `master::Master` is moved to `master::embedded::master` in preparation for Redis based implementation. -- `crate::mcaptcha` is moved to `master::embedded::mcaptcha` in preparation +- `crate::mcaptcha` is moved to `master::embedded::counter` in preparation for Redis based implementation. +- `AddSite` message for `Master` now requires an instance of + `crate::master::MCaptcha`. In the case of + `crate::master::embedded::master`, it automatically starts `Counter` + actor. + ## 0.1.3 ## Added diff --git a/examples/simple.rs b/examples/simple.rs index e61668d..9b9e2c1 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,6 +1,7 @@ use libmcaptcha::{ cache::{messages::VerifyCaptchaResult, HashCache}, - master::embedded::master::{AddSiteBuilder, Master}, + master::embedded::master::Master, + master::AddSiteBuilder, pow::{ConfigBuilder, Work}, system::SystemBuilder, DefenseBuilder, LevelBuilder, MCaptchaBuilder, @@ -75,8 +76,7 @@ async fn main() -> std::io::Result<()> { .duration(30) // .cache(cache) .build() - .unwrap() - .start(); + .unwrap(); // unique value identifying an MCaptcha actor let mcaptcha_name = "batsense.net"; @@ -84,7 +84,7 @@ async fn main() -> std::io::Result<()> { // add MCaptcha to Master let msg = AddSiteBuilder::default() .id(mcaptcha_name.into()) - .addr(mcaptcha.clone()) + .mcaptcha(mcaptcha) .build() .unwrap(); system.master.send(msg).await.unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 636feaa..25c525e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,8 @@ //! ```rust //! use libmcaptcha::{ //! cache::{messages::VerifyCaptchaResult, HashCache}, -//! master::embedded::master::{AddSiteBuilder, Master}, +//! master::embedded::master:: Master, +//! master::AddSiteBuilder, //! pow::{ConfigBuilder, Work}, //! system::SystemBuilder, //! DefenseBuilder, LevelBuilder, MCaptchaBuilder, @@ -123,8 +124,7 @@ //! .duration(30) //! // .cache(cache) //! .build() -//! .unwrap() -//! .start(); +//! .unwrap(); //! //! // unique value identifying an MCaptcha actor //! let mcaptcha_name = "batsense.net"; @@ -132,7 +132,7 @@ //! // add MCaptcha to Master //! let msg = AddSiteBuilder::default() //! .id(mcaptcha_name.into()) -//! .addr(mcaptcha.clone()) +//! .mcaptcha(mcaptcha) //! .build() //! .unwrap(); //! system.master.send(msg).await.unwrap(); @@ -196,4 +196,5 @@ mod utils; pub use crate::cache::hashcache::HashCache; pub use defense::{Defense, DefenseBuilder, LevelBuilder}; -pub use master::embedded::mcaptcha::{MCaptcha, MCaptchaBuilder}; +pub use master::embedded::counter::Counter; +pub use master::MCaptchaBuilder; diff --git a/src/master/embedded/mcaptcha.rs b/src/master/embedded/counter.rs similarity index 80% rename from src/master/embedded/mcaptcha.rs rename to src/master/embedded/counter.rs index c8d3977..bef65fb 100644 --- a/src/master/embedded/mcaptcha.rs +++ b/src/master/embedded/counter.rs @@ -15,11 +15,17 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -//! MCaptcha actor module that manages defense levels +//! Counter actor module that manages defense levels //! //! ## Usage: //! ```rust -//! use libmcaptcha::{master::embedded::mcaptcha::AddVisitor, MCaptchaBuilder, cache::HashCache, LevelBuilder, DefenseBuilder}; +//! use libmcaptcha::{ +//! master::embedded::counter::{Counter, AddVisitor}, +//! master::MCaptchaBuilder, +//! cache::HashCache, +//! LevelBuilder, +//! DefenseBuilder +//! }; //! // traits from actix needs to be in scope for starting actor //! use actix::prelude::*; //! @@ -55,18 +61,20 @@ //! .build() //! .unwrap(); //! -//! // create and start MCaptcha actor +//! // create and start Counter actor //! //let cache = HashCache::default().start(); //! let mcaptcha = MCaptchaBuilder::default() //! .defense(defense) //! // leaky bucket algorithm's emission interval //! .duration(30) //! .build() -//! .unwrap() -//! .start(); +//! .unwrap(); +//! +//! let counter: Counter = mcaptcha.into(); +//! let counter = counter.start(); //! //! // increment count when user visits protected routes -//! mcaptcha.send(AddVisitor).await.unwrap(); +//! counter.send(AddVisitor).await.unwrap(); //! //! Ok(()) //! } @@ -78,72 +86,29 @@ use std::time::Duration; use actix::dev::*; use serde::{Deserialize, Serialize}; -use crate::{ - defense::Defense, - errors::{CaptchaError, CaptchaResult}, - master::AddVisitorResult, -}; - -/// Builder for [MCaptcha] -#[derive(Clone, Serialize, Deserialize, Debug)] -pub struct MCaptchaBuilder { - visitor_threshold: u32, - defense: Option, - duration: Option, -} - -impl Default for MCaptchaBuilder { - fn default() -> Self { - MCaptchaBuilder { - visitor_threshold: 0, - defense: None, - duration: None, - } - } -} +use crate::master::MCaptcha; +use crate::{defense::Defense, master::AddVisitorResult}; /// This struct represents the mCaptcha state and is used /// to configure leaky-bucket lifetime and manage defense #[derive(Clone, Serialize, Deserialize, Debug)] -pub struct MCaptcha { +pub struct Counter { visitor_threshold: u32, defense: Defense, duration: u64, } - -impl MCaptchaBuilder { - /// set defense - pub fn defense(&mut self, d: Defense) -> &mut Self { - self.defense = Some(d); - self - } - - /// set duration - pub fn duration(&mut self, d: u64) -> &mut Self { - self.duration = Some(d); - self - } - - /// Builds new [MCaptcha] - pub fn build(&mut self) -> CaptchaResult { - if self.duration.is_none() { - Err(CaptchaError::PleaseSetValue("duration".into())) - } else if self.defense.is_none() { - Err(CaptchaError::PleaseSetValue("defense".into())) - } else if self.duration <= Some(0) { - Err(CaptchaError::CaptchaDurationZero) - } else { - let m = MCaptcha { - duration: self.duration.unwrap(), - defense: self.defense.clone().unwrap(), - visitor_threshold: self.visitor_threshold, - }; - Ok(m) - } +impl From for Counter { + fn from(m: MCaptcha) -> Counter { + let m = Counter { + duration: m.duration, + defense: m.defense, + visitor_threshold: m.visitor_threshold, + }; + m } } -impl MCaptcha { +impl Counter { /// increments the visitor count by one pub fn add_visitor(&mut self) { self.visitor_threshold += 1; @@ -166,12 +131,12 @@ impl MCaptcha { self.defense.get_difficulty() } - /// get [MCaptcha]'s lifetime + /// get [Counter]'s lifetime pub fn get_duration(&self) -> u64 { self.duration } } -impl Actor for MCaptcha { +impl Actor for Counter { type Context = Context; } @@ -180,7 +145,7 @@ impl Actor for MCaptcha { #[rtype(result = "()")] struct DeleteVisitor; -impl Handler for MCaptcha { +impl Handler for Counter { type Result = (); fn handle(&mut self, _msg: DeleteVisitor, _ctx: &mut Self::Context) -> Self::Result { self.decrement_visitor(); @@ -194,7 +159,7 @@ impl Handler for MCaptcha { pub struct AddVisitor; impl AddVisitorResult { - fn new(m: &MCaptcha) -> Self { + fn new(m: &Counter) -> Self { AddVisitorResult { duration: m.get_duration(), difficulty_factor: m.get_difficulty(), @@ -202,7 +167,7 @@ impl AddVisitorResult { } } -impl Handler for MCaptcha { +impl Handler for Counter { type Result = MessageResult; fn handle(&mut self, _: AddVisitor, ctx: &mut Self::Context) -> Self::Result { @@ -228,7 +193,7 @@ impl Handler for MCaptcha { #[rtype(result = "u32")] pub struct GetCurrentVisitorCount; -impl Handler for MCaptcha { +impl Handler for Counter { type Result = MessageResult; fn handle(&mut self, _: GetCurrentVisitorCount, _ctx: &mut Self::Context) -> Self::Result { @@ -236,12 +201,12 @@ impl Handler for MCaptcha { } } -/// Message to stop [MCaptcha] +/// Message to stop [Counter] #[derive(Message)] #[rtype(result = "()")] pub struct Stop; -impl Handler for MCaptcha { +impl Handler for Counter { type Result = (); fn handle(&mut self, _: Stop, ctx: &mut Self::Context) -> Self::Result { @@ -253,6 +218,8 @@ impl Handler for MCaptcha { pub mod tests { use super::*; use crate::defense::*; + use crate::errors::*; + use crate::master::MCaptchaBuilder; // constants for testing // (visitor count, level) @@ -260,7 +227,7 @@ pub mod tests { pub const LEVEL_2: (u32, u32) = (500, 500); pub const DURATION: u64 = 5; - type MyActor = Addr; + type MyActor = Addr; pub fn get_defense() -> Defense { DefenseBuilder::default() @@ -286,13 +253,17 @@ pub mod tests { .unwrap() } - async fn race(addr: Addr, count: (u32, u32)) { + async fn race(addr: Addr, count: (u32, u32)) { for _ in 0..count.0 as usize - 1 { let _ = addr.send(AddVisitor).await.unwrap(); } } - pub fn get_counter() -> MCaptcha { + pub fn get_counter() -> Counter { + get_mcaptcha().into() + } + + pub fn get_mcaptcha() -> MCaptcha { MCaptchaBuilder::default() .defense(get_defense()) .duration(DURATION) diff --git a/src/master/embedded/master.rs b/src/master/embedded/master.rs index 682a54a..438c18a 100644 --- a/src/master/embedded/master.rs +++ b/src/master/embedded/master.rs @@ -15,7 +15,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -//! [Master] actor module that manages [MCaptcha] actors +//! [Master] actor module that manages [Counter] actors use std::collections::BTreeMap; use std::sync::mpsc::channel; use std::time::Duration; @@ -23,30 +23,28 @@ use std::time::Duration; //use actix::clock::sleep; use actix::clock::delay_for; use actix::dev::*; -use derive_builder::Builder; use log::info; -use super::mcaptcha::MCaptcha; -use crate::master::AddVisitor; +use super::counter::Counter; use crate::master::Master as MasterTrait; +use crate::master::{AddSite, AddVisitor}; -/// This Actor manages the [MCaptcha] actors. -/// A service can have several [MCaptcha] actors with +/// This Actor manages the [Counter] actors. +/// A service can have several [Counter] actors with /// varying [Defense][crate::defense::Defense] configurations /// so a "master" actor is needed to manage them all #[derive(Clone, Default)] pub struct Master { - sites: BTreeMap, Addr)>, + sites: BTreeMap, Addr)>, gc: u64, } impl MasterTrait for Master {} impl Master { - /// add [MCaptcha] actor to [Master] - pub fn add_site(&mut self, details: AddSite) { - self.sites - .insert(details.id, (None, details.addr.to_owned())); + /// add [Counter] actor to [Master] + pub fn add_site(&mut self, addr: Addr, id: String) { + self.sites.insert(id, (None, addr.to_owned())); } /// create new master @@ -58,8 +56,8 @@ impl Master { } } - /// get [MCaptcha] actor from [Master] - pub fn get_site<'a, 'b>(&'a mut self, id: &'b str) -> Option> { + /// get [Counter] actor from [Master] + pub fn get_site<'a, 'b>(&'a mut self, id: &'b str) -> Option> { let mut r = None; if let Some((read_val, addr)) = self.sites.get_mut(id) { r = Some(addr.clone()); @@ -68,7 +66,7 @@ impl Master { r } - /// remvoes [MCaptcha] actor from [Master] + /// remvoes [Counter] actor from [Master] pub fn rm_site(&mut self, id: &str) { self.sites.remove(id); } @@ -99,7 +97,7 @@ impl Handler for Master { let fut = async move { let config = addr .unwrap() - .send(super::mcaptcha::AddVisitor) + .send(super::counter::AddVisitor) .await .unwrap(); @@ -112,9 +110,9 @@ impl Handler for Master { } } -/// Message to get an [MCaptcha] actor from master +/// Message to get an [Counter] actor from master #[derive(Message)] -#[rtype(result = "Option>")] +#[rtype(result = "Option>")] pub struct GetSite(pub String); impl Handler for Master { @@ -130,7 +128,7 @@ impl Handler for Master { } } -/// Message to clean up master of [MCaptcha] actors with zero visitor count +/// Message to clean up master of [Counter] actors with zero visitor count #[derive(Message)] #[rtype(result = "()")] pub struct CleanUp; @@ -145,7 +143,7 @@ impl Handler for Master { info!("init master actor cleanup up"); let task = async move { for (id, (new, addr)) in sites.iter() { - use super::mcaptcha::{GetCurrentVisitorCount, Stop}; + use super::counter::{GetCurrentVisitorCount, Stop}; let visitor_count = addr.send(GetCurrentVisitorCount).await.unwrap(); println!("{}", visitor_count); if visitor_count == 0 && new.is_some() { @@ -165,7 +163,7 @@ impl Handler for Master { } } -/// Message to delete [MCaptcha] actor +/// Message to delete [Counter] actor #[derive(Message)] #[rtype(result = "()")] pub struct RemoveSite(pub String); @@ -178,43 +176,38 @@ impl Handler for Master { } } -/// Message to add an [MCaptcha] actor to [Master] -#[derive(Message, Builder)] -#[rtype(result = "()")] -pub struct AddSite { - pub id: String, - pub addr: Addr, -} - impl Handler for Master { type Result = (); fn handle(&mut self, m: AddSite, _ctx: &mut Self::Context) -> Self::Result { - self.add_site(m); + let counter: Counter = m.mcaptcha.into(); + let addr = counter.start(); + self.add_site(addr, m.id); } } #[cfg(test)] mod tests { use super::*; - use crate::master::embedded::mcaptcha::tests::*; + use crate::master::embedded::counter::tests::*; + use crate::master::AddSiteBuilder; #[actix_rt::test] async fn master_actor_works() { let addr = Master::new(1).start(); let id = "yo"; - let mcaptcha = get_counter().start(); + let mcaptcha = get_mcaptcha(); let msg = AddSiteBuilder::default() .id(id.into()) - .addr(mcaptcha.clone()) + .mcaptcha(mcaptcha.clone()) .build() .unwrap(); addr.send(msg).await.unwrap(); let mcaptcha_addr = addr.send(GetSite(id.into())).await.unwrap(); - assert_eq!(mcaptcha_addr, Some(mcaptcha)); + assert!(mcaptcha_addr.is_some()); let addr_doesnt_exist = addr.send(GetSite("a".into())).await.unwrap(); assert!(addr_doesnt_exist.is_none()); diff --git a/src/master/embedded/mod.rs b/src/master/embedded/mod.rs index 2de680d..ecd66b1 100644 --- a/src/master/embedded/mod.rs +++ b/src/master/embedded/mod.rs @@ -16,5 +16,5 @@ * along with this program. If not, see . */ +pub mod counter; pub mod master; -pub mod mcaptcha; diff --git a/src/master/mod.rs b/src/master/mod.rs index d297b99..bddc455 100644 --- a/src/master/mod.rs +++ b/src/master/mod.rs @@ -19,12 +19,18 @@ use std::sync::mpsc::Receiver; use actix::dev::*; +use derive_builder::Builder; use serde::{Deserialize, Serialize}; pub mod embedded; +use crate::defense::Defense; +use crate::errors::*; + /// Describes actor handler trait impls that are required by a cache implementation -pub trait Master: actix::Actor + actix::Handler {} +pub trait Master: actix::Actor + actix::Handler + actix::Handler {} + +//+ actix::Handler /// Message to add visitor to an [MCaptcha] actor #[derive(Message)] @@ -39,3 +45,68 @@ pub struct AddVisitorResult { pub duration: u64, pub difficulty_factor: u32, } + +/// Message to add an [Counter] actor to [Master] +#[derive(Message, Builder)] +#[rtype(result = "()")] +pub struct AddSite { + pub id: String, + pub mcaptcha: MCaptcha, +} + +/// Builder for [MCaptcha] +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct MCaptchaBuilder { + visitor_threshold: u32, + defense: Option, + duration: Option, +} + +impl Default for MCaptchaBuilder { + fn default() -> Self { + MCaptchaBuilder { + visitor_threshold: 0, + defense: None, + duration: None, + } + } +} + +impl MCaptchaBuilder { + /// set defense + pub fn defense(&mut self, d: Defense) -> &mut Self { + self.defense = Some(d); + self + } + + /// set duration + pub fn duration(&mut self, d: u64) -> &mut Self { + self.duration = Some(d); + self + } + + /// Builds new [MCaptcha] + pub fn build(self: &mut MCaptchaBuilder) -> CaptchaResult { + if self.duration.is_none() { + Err(CaptchaError::PleaseSetValue("duration".into())) + } else if self.defense.is_none() { + Err(CaptchaError::PleaseSetValue("defense".into())) + } else if self.duration <= Some(0) { + Err(CaptchaError::CaptchaDurationZero) + } else { + let m = MCaptcha { + duration: self.duration.unwrap(), + defense: self.defense.clone().unwrap(), + visitor_threshold: self.visitor_threshold, + }; + Ok(m) + } + } +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct MCaptcha { + visitor_threshold: u32, + defense: Defense, + duration: u64, +} diff --git a/src/system.rs b/src/system.rs index 72cc5e3..72ced7c 100644 --- a/src/system.rs +++ b/src/system.rs @@ -42,7 +42,8 @@ where + ToEnvelope + ToEnvelope, X: Master, - ::Context: ToEnvelope, + ::Context: + ToEnvelope + ToEnvelope, { /// utility function to get difficulty factor of site `id` and cache it pub async fn get_pow(&self, id: String) -> Option { @@ -123,21 +124,21 @@ mod tests { use super::System; use super::*; use crate::cache::HashCache; + use crate::master::embedded::counter::tests::*; use crate::master::embedded::master::Master; - use crate::master::embedded::master::*; - use crate::master::embedded::mcaptcha::tests::*; + use crate::master::*; const MCAPTCHA_NAME: &str = "batsense.net"; async fn boostrap_system(gc: u64) -> System { let master = Master::new(gc).start(); - let mcaptcha = get_counter().start(); + let mcaptcha = get_mcaptcha(); let pow = get_config(); let cache = HashCache::default().start(); let msg = AddSiteBuilder::default() .id(MCAPTCHA_NAME.into()) - .addr(mcaptcha.clone()) + .mcaptcha(mcaptcha) .build() .unwrap();