doc and better names

This commit is contained in:
Aravinth Manivannan 2021-03-02 11:09:29 +05:30
parent 0356bb1329
commit a884edf2f2
Signed by: realaravinth
GPG Key ID: AD9F0F08E855ED88
5 changed files with 320 additions and 143 deletions

24
Cargo.lock generated
View File

@ -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"

View File

@ -1,49 +1,108 @@
/*
* mCaptcha - A proof of work based DoS protection system
* Copyright © 2021 Aravinth Manivannan <realravinth@batsense.net>
*
* 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 <http://www.gnu.org/licenses/>.
*/
//! 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<Self>;
}
impl Handler<Visitor> for Counter {
impl Handler<Visitor> 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<Visitor> for Counter {
}
}
impl Handler<DeleteVisitor> for Counter {
impl Handler<DeleteVisitor> 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<DeleteVisitor> 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<Counter>, count: (u32, u32)) {
async fn race(addr: Addr<MCaptcha>, 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()

View File

@ -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 <realravinth@batsense.net>
*
* 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 <http://www.gnu.org/licenses/>.
*/
//! 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<u32>,
visitor_threshold: Option<u32>,
difficulty_factor: Option<u32>,
}
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<Level> {
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<Level>,
@ -102,6 +122,7 @@ pub struct Defense {
current_visitor_threshold: usize,
}
/// Builder struct for [Defense]
#[derive(Debug, Clone, PartialEq)]
pub struct DefenseBuilder {
levels: Vec<Level>,
@ -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<Defense> {
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);
// }
}

View File

@ -1,18 +1,36 @@
//! Error datatypes
/*
* mCaptcha - A proof of work based DoS protection system
* Copyright © 2021 Aravinth Manivannan <realravinth@batsense.net>
*
* 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 <http://www.gnu.org/licenses/>.
*/
//! 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,

View File

@ -1,3 +1,103 @@
/*
* mCaptcha - A proof of work based DoS protection system
* Copyright © 2021 Aravinth Manivannan <realravinth@batsense.net>
*
* 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 <http://www.gnu.org/licenses/>.
*/
//! 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};