From a884edf2f2bd9f5bce506127b287c9d7c81f21af Mon Sep 17 00:00:00 2001 From: realaravinth Date: Tue, 2 Mar 2021 11:09:29 +0530 Subject: [PATCH] doc and better names --- Cargo.lock | 24 ++--- src/counter.rs | 131 ++++++++++++++++++------- src/{levels.rs => defense.rs} | 174 +++++++++++++++++----------------- src/errors.rs | 32 +++++-- src/lib.rs | 102 +++++++++++++++++++- 5 files changed, 320 insertions(+), 143 deletions(-) rename src/{levels.rs => defense.rs} (71%) diff --git a/Cargo.lock b/Cargo.lock index d0026b7..013372c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -252,18 +252,6 @@ dependencies = [ "vec_map", ] -[[package]] -name = "cli" -version = "0.1.0" -dependencies = [ - "clap", - "clipboard", - "pow_sha256", - "serde", - "serde_derive", - "serde_json", -] - [[package]] name = "clipboard" version = "0.5.0" @@ -730,6 +718,18 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +[[package]] +name = "mcaptcha-cli" +version = "0.1.0" +dependencies = [ + "clap", + "clipboard", + "pow_sha256", + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "memchr" version = "2.3.4" diff --git a/src/counter.rs b/src/counter.rs index f32c83e..d0a10e2 100644 --- a/src/counter.rs +++ b/src/counter.rs @@ -1,49 +1,108 @@ +/* + * mCaptcha - A proof of work based DoS protection system + * Copyright © 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * 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 +//! +//! ## Usage: +//! ```rust +//! use m_captcha::{message::Visitor,MCaptchaBuilder, LevelBuilder, DefenseBuilder}; +//! // traits from actix needs to be in scope for starting actor +//! use actix::prelude::*; +//! +//! #[actix_rt::main] +//! async fn main() -> std::io::Result<()> { +//! // configure defense +//! let defense = DefenseBuilder::default() +//! // add as many levels as you see fit +//! .add_level( +//! LevelBuilder::default() +//! // visitor_threshold is the threshold/limit at which +//! // mCaptcha will adjust difficulty levels +//! // it is advisable to set small values for the first +//! // levels visitor_threshold and difficulty_factor +//! // as this will be the work that clients will be +//! // computing when there's no load +//! .visitor_threshold(50) +//! .difficulty_factor(500) +//! .unwrap() +//! .build() +//! .unwrap(), +//! ) +//! .unwrap() +//! .add_level( +//! LevelBuilder::default() +//! .visitor_threshold(5000) +//! .difficulty_factor(50000) +//! .unwrap() +//! .build() +//! .unwrap(), +//! ) +//! .unwrap() +//! .build() +//! .unwrap(); +//! +//! // create and start MCaptcha actor +//! let mcaptcha = MCaptchaBuilder::default() +//! .defense(defense) +//! // leaky bucket algorithm's emission interval +//! .duration(30) +//! .build() +//! .unwrap() +//! .start(); +//! +//! // increment count when user visits protected routes +//! mcaptcha.send(Visitor).await.unwrap(); +//! +//! Ok(()) +//! } +//! ``` + use std::time::Duration; use actix::prelude::*; use derive_builder::Builder; -//use lazy_static::*; -use crate::levels::Defense; -//use crate::new_levels::Levels; +use crate::defense::Defense; -// TODO move this into config parameter -// lazy_static! { -// pub static ref DURATION: Duration = Duration::new(POW_SESSION_DURATION, 0); -// } - -/// Add visitor message +/// Message to increment the visitor count #[derive(Message)] #[rtype(result = "u32")] pub struct Visitor; +/// Message to decrement the visitor count #[derive(Message)] #[rtype(result = "()")] struct DeleteVisitor; +/// This struct represents the mCaptcha state and is used +/// to configure leaky-bucket lifetime and manage defense #[derive(Builder)] -pub struct Counter { +pub struct MCaptcha { #[builder(default = "0", setter(skip))] - visitor_count: u32, + visitor_threshold: u32, defense: Defense, duration: u64, } -// impl Default for Counter { -// fn default() -> Self { -// Counter { -// visitor_count: 0, -// levels: Levels::default(), -// duration: 30, -// } -// } -// } - -impl Counter { +impl MCaptcha { /// incerment visiotr count by one pub fn add_visitor(&mut self) { - self.visitor_count += 1; - if self.visitor_count > self.defense.visitor_threshold() { + self.visitor_threshold += 1; + if self.visitor_threshold > self.defense.visitor_threshold() { self.defense.tighten_up(); } else { self.defense.loosen_up(); @@ -52,8 +111,8 @@ impl Counter { /// deccerment visiotr count by one pub fn decrement_visiotr(&mut self) { - if self.visitor_count > 0 { - self.visitor_count -= 1; + if self.visitor_threshold > 0 { + self.visitor_threshold -= 1; } } @@ -63,11 +122,11 @@ impl Counter { } } -impl Actor for Counter { +impl Actor for MCaptcha { type Context = Context; } -impl Handler for Counter { +impl Handler for MCaptcha { type Result = u32; fn handle(&mut self, _: Visitor, ctx: &mut Self::Context) -> Self::Result { use actix::clock::delay_for; @@ -87,7 +146,7 @@ impl Handler for Counter { } } -impl Handler for Counter { +impl Handler for MCaptcha { type Result = (); fn handle(&mut self, _msg: DeleteVisitor, _ctx: &mut Self::Context) -> Self::Result { self.decrement_visiotr(); @@ -97,9 +156,9 @@ impl Handler for Counter { #[cfg(test)] mod tests { use super::*; - use crate::levels::*; + use crate::defense::*; - // constants foor testing + // constants for testing // (visitor count, level) const LEVEL_1: (u32, u32) = (50, 50); const LEVEL_2: (u32, u32) = (500, 500); @@ -109,7 +168,7 @@ mod tests { DefenseBuilder::default() .add_level( LevelBuilder::default() - .visitor_count(LEVEL_1.0) + .visitor_threshold(LEVEL_1.0) .difficulty_factor(LEVEL_1.1) .unwrap() .build() @@ -118,7 +177,7 @@ mod tests { .unwrap() .add_level( LevelBuilder::default() - .visitor_count(LEVEL_2.0) + .visitor_threshold(LEVEL_2.0) .difficulty_factor(LEVEL_2.1) .unwrap() .build() @@ -129,14 +188,14 @@ 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(Visitor).await.unwrap(); } } - fn get_counter() -> Counter { - CounterBuilder::default() + fn get_counter() -> MCaptcha { + MCaptchaBuilder::default() .defense(get_defense()) .duration(DURATION) .build() diff --git a/src/levels.rs b/src/defense.rs similarity index 71% rename from src/levels.rs rename to src/defense.rs index bf4869c..f676fcc 100644 --- a/src/levels.rs +++ b/src/defense.rs @@ -1,56 +1,76 @@ -// //! ```rust -// //! DefenseBuilder::default() -// //! .add_level( -// //! LevelBuilder::default() -// //! .visitor_count(50) -// //! .difficulty_factor(50) -// //! .unwrap() -// //! .build() -// //! .unwrap(), -// //! ) -// //! .unwrap() -// //! .add_level( -// //! LevelBuilder::default() -// //! .visitor_count(500) -// //! .difficulty_factor(500) -// //! .unwrap() -// //! .build() -// //! .unwrap(), -// //! ) -// //! .unwrap() -// //! .build() -// //! .unwrap(); -// //! ``` +/* + * mCaptcha - A proof of work based DoS protection system + * Copyright © 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +//! Defense datatypes +//! ```rust +//! use m_captcha::{LevelBuilder, DefenseBuilder}; +//! DefenseBuilder::default() +//! .add_level( +//! LevelBuilder::default() +//! .visitor_threshold(50) +//! .difficulty_factor(50) +//! .unwrap() +//! .build() +//! .unwrap(), +//! ) +//! .unwrap() +//! .add_level( +//! LevelBuilder::default() +//! .visitor_threshold(500) +//! .difficulty_factor(500) +//! .unwrap() +//! .build() +//! .unwrap(), +//! ) +//! .unwrap() +//! .build() +//! .unwrap(); +//! ``` use crate::errors::*; -/// Level struct +/// Level struct that describes threshold-difficulty factor mapping #[derive(Debug, Copy, Clone, PartialEq)] pub struct Level { - visitor_count: u32, + visitor_threshold: u32, difficulty_factor: u32, } impl Default for Level { fn default() -> Self { Level { - visitor_count: 0, + visitor_threshold: 0, difficulty_factor: 0, } } } -/// set difficulty configuration +/// Bulder struct for [Level] to describe threshold-difficulty factor mapping #[derive(Debug, Copy, Clone, PartialEq)] pub struct LevelBuilder { - visitor_count: Option, + visitor_threshold: Option, difficulty_factor: Option, } impl Default for LevelBuilder { fn default() -> Self { LevelBuilder { - visitor_count: None, + visitor_threshold: None, difficulty_factor: None, } } @@ -58,14 +78,13 @@ impl Default for LevelBuilder { impl LevelBuilder { /// set visitor count for level - pub fn visitor_count(&mut self, visitor_count: u32) -> &mut Self { - self.visitor_count = Some(visitor_count); + pub fn visitor_threshold(&mut self, visitor_threshold: u32) -> &mut Self { + self.visitor_threshold = Some(visitor_threshold); self } - /// set difficulty factor for level - /// difficulty_factor can't be zero because - /// Difficulty is calculated as + /// set difficulty factor for level. difficulty_factor can't be zero because + /// Difficulty is calculated as: /// ```no_run /// let difficulty_factor = 500; /// let difficulty = u128::max_value() - u128::max_value() / difficulty_factor; @@ -80,21 +99,22 @@ impl LevelBuilder { } } - /// build Level + /// build Level struct pub fn build(&mut self) -> CaptchaResult { - if self.visitor_count.is_none() { + if self.visitor_threshold.is_none() { Err(CaptchaError::SetVisitorCount) } else if self.difficulty_factor.is_none() { Err(CaptchaError::SetDifficultyFactor) } else { Ok(Level { difficulty_factor: self.difficulty_factor.unwrap(), - visitor_count: self.visitor_count.unwrap(), + visitor_threshold: self.visitor_threshold.unwrap(), }) } } } +/// struct describes all the different [Level]s at which an mCaptcha system operates #[derive(Debug, Clone, PartialEq)] pub struct Defense { levels: Vec, @@ -102,6 +122,7 @@ pub struct Defense { current_visitor_threshold: usize, } +/// Builder struct for [Defense] #[derive(Debug, Clone, PartialEq)] pub struct DefenseBuilder { levels: Vec, @@ -114,9 +135,10 @@ impl Default for DefenseBuilder { } impl DefenseBuilder { + /// add a level to [Defense] pub fn add_level(&mut self, level: Level) -> CaptchaResult<&mut Self> { for i in self.levels.iter() { - if i.visitor_count == level.visitor_count { + if i.visitor_threshold == level.visitor_threshold { return Err(CaptchaError::DuplicateVisitorCount); } } @@ -124,12 +146,11 @@ impl DefenseBuilder { Ok(self) } + /// Build [Defense] pub fn build(&mut self) -> CaptchaResult { if !self.levels.is_empty() { // sort levels to arrange in ascending order - self.levels.sort_by_key(|a| a.visitor_count); - - // as visitor count increases, difficulty_factor too should increse + self.levels.sort_by_key(|a| a.visitor_threshold); for level in self.levels.iter() { if level.difficulty_factor == 0 { @@ -137,6 +158,8 @@ impl DefenseBuilder { } } + // as visitor count increases, difficulty_factor too should increse + // if it decreses, an error must be thrown for i in 0..self.levels.len() - 1 { if self.levels[i].difficulty_factor > self.levels[i + 1].difficulty_factor { return Err(CaptchaError::DecreaseingDifficultyFactor); @@ -163,44 +186,45 @@ impl Default for Defense { } impl Defense { - ///! Difficulty is calculated as + ///! Difficulty is calculated as: ///! ```rust ///! let difficulty = u128::max_value() - u128::max_value() / difficulty_factor; ///! ``` - ///! the higher the `difficulty_factor`, the higher the difficulty. + ///! The higher the `difficulty_factor`, the higher the difficulty. - /// get difficulty factor of current level of defense + /// Get difficulty factor of current level of defense pub fn get_difficulty(&self) -> u32 { self.levels[self.current_visitor_threshold].difficulty_factor } - /// tighten up defense. Increases defense level by a factor of one - /// when defense is at max level, calling this method will have no effect + /// tighten up defense. Increases defense level by a factor of one. + /// When defense is at max level, calling this method will have no effect pub fn tighten_up(&mut self) { if self.current_visitor_threshold != self.levels.len() - 1 { self.current_visitor_threshold += 1; } } - /// loosen up defense. Decreases defense level by a factor of one - /// when defense is at the lowest level, calling this method will have no effect + /// Loosen up defense. Decreases defense level by a factor of one. + /// When defense is at the lowest level, calling this method will have no effect. pub fn loosen_up(&mut self) { if self.current_visitor_threshold != 0 { self.current_visitor_threshold -= 1; } } - /// set defense to maximum level + /// Set defense to maximum level pub fn max_defense(&mut self) { self.current_visitor_threshold = self.levels.len() - 1; } - /// set defense to minimum level + /// Set defense to minimum level pub fn min_defense(&mut self) { self.current_visitor_threshold = 0; } + /// Get current level's visitor threshold pub fn visitor_threshold(&self) -> u32 { - self.levels[self.current_visitor_threshold].visitor_count + self.levels[self.current_visitor_threshold].visitor_threshold } } @@ -213,11 +237,11 @@ mod tests { let level = LevelBuilder::default() .difficulty_factor(1) .unwrap() - .visitor_count(0) + .visitor_threshold(0) .build() .unwrap(); - assert_eq!(level.visitor_count, 0); + assert_eq!(level.visitor_threshold, 0); assert_eq!(level.difficulty_factor, 1); assert_eq!( @@ -227,12 +251,12 @@ mod tests { } #[test] - fn defense_builder_duplicate_visitor_count() { + fn defense_builder_duplicate_visitor_threshold() { let mut defense_builder = DefenseBuilder::default(); let err = defense_builder .add_level( LevelBuilder::default() - .visitor_count(50) + .visitor_threshold(50) .difficulty_factor(50) .unwrap() .build() @@ -241,7 +265,7 @@ mod tests { .unwrap() .add_level( LevelBuilder::default() - .visitor_count(50) + .visitor_threshold(50) .difficulty_factor(50) .unwrap() .build() @@ -256,7 +280,7 @@ mod tests { let err = defense_builder .add_level( LevelBuilder::default() - .visitor_count(50) + .visitor_threshold(50) .difficulty_factor(50) .unwrap() .build() @@ -265,7 +289,7 @@ mod tests { .unwrap() .add_level( LevelBuilder::default() - .visitor_count(500) + .visitor_threshold(500) .difficulty_factor(10) .unwrap() .build() @@ -280,7 +304,7 @@ mod tests { DefenseBuilder::default() .add_level( LevelBuilder::default() - .visitor_count(50) + .visitor_threshold(50) .difficulty_factor(50) .unwrap() .build() @@ -289,7 +313,7 @@ mod tests { .unwrap() .add_level( LevelBuilder::default() - .visitor_count(500) + .visitor_threshold(500) .difficulty_factor(5000) .unwrap() .build() @@ -298,7 +322,7 @@ mod tests { .unwrap() .add_level( LevelBuilder::default() - .visitor_count(5000) + .visitor_threshold(5000) .difficulty_factor(50000) .unwrap() .build() @@ -307,7 +331,7 @@ mod tests { .unwrap() .add_level( LevelBuilder::default() - .visitor_count(50000) + .visitor_threshold(50000) .difficulty_factor(500000) .unwrap() .build() @@ -316,7 +340,7 @@ mod tests { .unwrap() .add_level( LevelBuilder::default() - .visitor_count(500000) + .visitor_threshold(500000) .difficulty_factor(5000000) .unwrap() .build() @@ -395,28 +419,4 @@ mod tests { defense.loosen_up(); assert_eq!(defense.get_difficulty(), 50); } - - // #[test] - // fn threshold_works() { - // let mut level = Levels::default(); - // - // assert_eq!(level.threshold(), Levels::One as usize); - // level.next(); - // assert_eq!(level.threshold(), Levels::Two as usize); - // level.next(); - // assert_eq!(level.threshold(), Levels::Three as usize); - // } - // - // #[test] - // fn difficulty_works() { - // let mut level = Levels::default(); - // - // assert_eq!(level.get_difficulty(), Levels::One as u32); - // level.next(); - // assert_eq!(level.get_difficulty(), Levels::Two as u32); - // level.next(); - // assert_eq!(level.get_difficulty(), 100_000); - // level.next(); - // assert_eq!(level.get_difficulty(), 1_000_000); - // } } diff --git a/src/errors.rs b/src/errors.rs index 3e6a193..28d3f0e 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,18 +1,36 @@ -//! Error datatypes +/* + * mCaptcha - A proof of work based DoS protection system + * Copyright © 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +//! Errors and Result module use derive_more::{Display, Error}; -/// Errors that can occur when using +/// Error datatype #[derive(Debug, PartialEq, Display, Clone, Error)] #[cfg(not(tarpaulin_include))] pub enum CaptchaError { - /// when configuring m_captcha, [DefenseBuilder][crate::new_levels::DefenseBuilder] must be passed atleast - /// one `LevelConfig` if not this error will arise + /// When configuring m_captcha, [DefenseBuilder][crate::defense::DefenseBuilder] + /// must be passed atleast one `LevelConfig` if not this error will arise #[display(fmt = "LevelBuilder should have atleaset one level configured")] LevelEmpty, - /// Visitor count must be an integer - /// when configuring m_captcha, [LevelBuilder][crate::new_levels::LevelBuilder] difficulty_factor - /// must be set to greater than zero. + /// Visitor count must be a whole number(zero and above). + /// When configuring m_captcha, [LevelBuilder][crate::defense::LevelBuilder]. + /// difficulty_factor must be set to greater than zero. #[display(fmt = "difficulty factor must be greater than zero")] DifficultyFactorZero, diff --git a/src/lib.rs b/src/lib.rs index 350d9c9..78a832f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,103 @@ +/* + * mCaptcha - A proof of work based DoS protection system + * Copyright © 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +//! mCaptcha is a proof of work based Denaial-of-Service attack protection system. +//! This is is a server library that you can embed in your services to protect your +//! servers. +//! +//! A commercial managed solution is in the works but I'd much rather prefer +//! folks host their own instances as it will make the more decentralized and free. +//! +//! In mCaptcha, defense is adjusted in discrete levels that depend on the +//! ammount of traffic that a service is experiencing. So users of this library are +//! requested to benchmark their target machines before configuring their mCaptcha +//! component. +//! +//! ## Terminology: +//! - Difficulty(Factor): Minimum ammount of work that a client must do to make a valid +//! request. +//! - [Defense]: A datatype that various visitor-difficulty mappigns +//! - [Visitor][crate::message::Visitor]: Smallest unit of traffic, usually a single request. The more you have, the busier +//! your service is. Determines mCaptcha defense defense +//! - Visitor threshold: The threshold at which [MCaptcha] will adjust defense defense +//! +//! ## Example: +//! +//! ```rust +//! use m_captcha::{LevelBuilder, DefenseBuilder, message::Visitor, MCaptchaBuilder}; +//! // traits from actix needs to be in scope for starting actor +//! use actix::prelude::*; +//! +//! #[actix_rt::main] +//! async fn main() -> std::io::Result<()> { +//! // configure defense +//! let defense = DefenseBuilder::default() +//! // add as many defense as you see fit +//! .add_level( +//! LevelBuilder::default() +//! // visitor_threshold is the threshold/limit at which +//! // mCaptcha will adjust difficulty defense +//! // it is advisable to set small values for the first +//! // defense visitor_threshold and difficulty_factor +//! // as this will be the work that clients will be +//! // computing when there's no load +//! .visitor_threshold(50) +//! .difficulty_factor(500) +//! .unwrap() +//! .build() +//! .unwrap(), +//! ) +//! .unwrap() +//! .add_level( +//! LevelBuilder::default() +//! .visitor_threshold(5000) +//! .difficulty_factor(50000) +//! .unwrap() +//! .build() +//! .unwrap(), +//! ) +//! .unwrap() +//! .build() +//! .unwrap(); +//! +//! // create and start MCaptcha actor +//! let mcaptcha = MCaptchaBuilder::default() +//! .defense(defense) +//! // leaky bucket algorithm's emission interval +//! .duration(30) +//! .build() +//! .unwrap() +//! .start(); +//! +//! // increment count when user visits protected routes +//! mcaptcha.send(Visitor).await.unwrap(); +//! +//! Ok(()) +//! } +//! ``` + pub mod counter; +pub mod defense; pub mod errors; -pub mod levels; + +/// message datatypes to interact with [MCaptcha] actor +pub mod message { + pub use crate::counter::Visitor; +} + +pub use counter::{MCaptcha, MCaptchaBuilder}; +pub use defense::{Defense, DefenseBuilder, LevelBuilder};