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", "vec_map",
] ]
[[package]]
name = "cli"
version = "0.1.0"
dependencies = [
"clap",
"clipboard",
"pow_sha256",
"serde",
"serde_derive",
"serde_json",
]
[[package]] [[package]]
name = "clipboard" name = "clipboard"
version = "0.5.0" version = "0.5.0"
@ -730,6 +718,18 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
name = "mcaptcha-cli"
version = "0.1.0"
dependencies = [
"clap",
"clipboard",
"pow_sha256",
"serde",
"serde_derive",
"serde_json",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.3.4" 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 std::time::Duration;
use actix::prelude::*; use actix::prelude::*;
use derive_builder::Builder; use derive_builder::Builder;
//use lazy_static::*;
use crate::levels::Defense; use crate::defense::Defense;
//use crate::new_levels::Levels;
// TODO move this into config parameter /// Message to increment the visitor count
// lazy_static! {
// pub static ref DURATION: Duration = Duration::new(POW_SESSION_DURATION, 0);
// }
/// Add visitor message
#[derive(Message)] #[derive(Message)]
#[rtype(result = "u32")] #[rtype(result = "u32")]
pub struct Visitor; pub struct Visitor;
/// Message to decrement the visitor count
#[derive(Message)] #[derive(Message)]
#[rtype(result = "()")] #[rtype(result = "()")]
struct DeleteVisitor; struct DeleteVisitor;
/// This struct represents the mCaptcha state and is used
/// to configure leaky-bucket lifetime and manage defense
#[derive(Builder)] #[derive(Builder)]
pub struct Counter { pub struct MCaptcha {
#[builder(default = "0", setter(skip))] #[builder(default = "0", setter(skip))]
visitor_count: u32, visitor_threshold: u32,
defense: Defense, defense: Defense,
duration: u64, duration: u64,
} }
// impl Default for Counter { impl MCaptcha {
// fn default() -> Self {
// Counter {
// visitor_count: 0,
// levels: Levels::default(),
// duration: 30,
// }
// }
// }
impl Counter {
/// incerment visiotr count by one /// incerment visiotr count by one
pub fn add_visitor(&mut self) { pub fn add_visitor(&mut self) {
self.visitor_count += 1; self.visitor_threshold += 1;
if self.visitor_count > self.defense.visitor_threshold() { if self.visitor_threshold > self.defense.visitor_threshold() {
self.defense.tighten_up(); self.defense.tighten_up();
} else { } else {
self.defense.loosen_up(); self.defense.loosen_up();
@ -52,8 +111,8 @@ impl Counter {
/// deccerment visiotr count by one /// deccerment visiotr count by one
pub fn decrement_visiotr(&mut self) { pub fn decrement_visiotr(&mut self) {
if self.visitor_count > 0 { if self.visitor_threshold > 0 {
self.visitor_count -= 1; self.visitor_threshold -= 1;
} }
} }
@ -63,11 +122,11 @@ impl Counter {
} }
} }
impl Actor for Counter { impl Actor for MCaptcha {
type Context = Context<Self>; type Context = Context<Self>;
} }
impl Handler<Visitor> for Counter { impl Handler<Visitor> for MCaptcha {
type Result = u32; type Result = u32;
fn handle(&mut self, _: Visitor, ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, _: Visitor, ctx: &mut Self::Context) -> Self::Result {
use actix::clock::delay_for; 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 = (); type Result = ();
fn handle(&mut self, _msg: DeleteVisitor, _ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, _msg: DeleteVisitor, _ctx: &mut Self::Context) -> Self::Result {
self.decrement_visiotr(); self.decrement_visiotr();
@ -97,9 +156,9 @@ impl Handler<DeleteVisitor> for Counter {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::levels::*; use crate::defense::*;
// constants foor testing // constants for testing
// (visitor count, level) // (visitor count, level)
const LEVEL_1: (u32, u32) = (50, 50); const LEVEL_1: (u32, u32) = (50, 50);
const LEVEL_2: (u32, u32) = (500, 500); const LEVEL_2: (u32, u32) = (500, 500);
@ -109,7 +168,7 @@ mod tests {
DefenseBuilder::default() DefenseBuilder::default()
.add_level( .add_level(
LevelBuilder::default() LevelBuilder::default()
.visitor_count(LEVEL_1.0) .visitor_threshold(LEVEL_1.0)
.difficulty_factor(LEVEL_1.1) .difficulty_factor(LEVEL_1.1)
.unwrap() .unwrap()
.build() .build()
@ -118,7 +177,7 @@ mod tests {
.unwrap() .unwrap()
.add_level( .add_level(
LevelBuilder::default() LevelBuilder::default()
.visitor_count(LEVEL_2.0) .visitor_threshold(LEVEL_2.0)
.difficulty_factor(LEVEL_2.1) .difficulty_factor(LEVEL_2.1)
.unwrap() .unwrap()
.build() .build()
@ -129,14 +188,14 @@ mod tests {
.unwrap() .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 { for _ in 0..count.0 as usize - 1 {
let _ = addr.send(Visitor).await.unwrap(); let _ = addr.send(Visitor).await.unwrap();
} }
} }
fn get_counter() -> Counter { fn get_counter() -> MCaptcha {
CounterBuilder::default() MCaptchaBuilder::default()
.defense(get_defense()) .defense(get_defense())
.duration(DURATION) .duration(DURATION)
.build() .build()

View file

@ -1,56 +1,76 @@
// //! ```rust /*
// //! DefenseBuilder::default() * mCaptcha - A proof of work based DoS protection system
// //! .add_level( * Copyright © 2021 Aravinth Manivannan <realravinth@batsense.net>
// //! LevelBuilder::default() *
// //! .visitor_count(50) * This program is free software: you can redistribute it and/or modify
// //! .difficulty_factor(50) * it under the terms of the GNU Affero General Public License as
// //! .unwrap() * published by the Free Software Foundation, either version 3 of the
// //! .build() * License, or (at your option) any later version.
// //! .unwrap(), *
// //! ) * This program is distributed in the hope that it will be useful,
// //! .unwrap() * but WITHOUT ANY WARRANTY; without even the implied warranty of
// //! .add_level( * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// //! LevelBuilder::default() * GNU Affero General Public License for more details.
// //! .visitor_count(500) *
// //! .difficulty_factor(500) * You should have received a copy of the GNU Affero General Public License
// //! .unwrap() * along with this program. If not, see <http://www.gnu.org/licenses/>.
// //! .build() */
// //! .unwrap(),
// //! ) //! Defense datatypes
// //! .unwrap() //! ```rust
// //! .build() //! use m_captcha::{LevelBuilder, DefenseBuilder};
// //! .unwrap(); //! 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::*; use crate::errors::*;
/// Level struct /// Level struct that describes threshold-difficulty factor mapping
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub struct Level { pub struct Level {
visitor_count: u32, visitor_threshold: u32,
difficulty_factor: u32, difficulty_factor: u32,
} }
impl Default for Level { impl Default for Level {
fn default() -> Self { fn default() -> Self {
Level { Level {
visitor_count: 0, visitor_threshold: 0,
difficulty_factor: 0, difficulty_factor: 0,
} }
} }
} }
/// set difficulty configuration /// Bulder struct for [Level] to describe threshold-difficulty factor mapping
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub struct LevelBuilder { pub struct LevelBuilder {
visitor_count: Option<u32>, visitor_threshold: Option<u32>,
difficulty_factor: Option<u32>, difficulty_factor: Option<u32>,
} }
impl Default for LevelBuilder { impl Default for LevelBuilder {
fn default() -> Self { fn default() -> Self {
LevelBuilder { LevelBuilder {
visitor_count: None, visitor_threshold: None,
difficulty_factor: None, difficulty_factor: None,
} }
} }
@ -58,14 +78,13 @@ impl Default for LevelBuilder {
impl LevelBuilder { impl LevelBuilder {
/// set visitor count for level /// set visitor count for level
pub fn visitor_count(&mut self, visitor_count: u32) -> &mut Self { pub fn visitor_threshold(&mut self, visitor_threshold: u32) -> &mut Self {
self.visitor_count = Some(visitor_count); self.visitor_threshold = Some(visitor_threshold);
self self
} }
/// set difficulty factor for level /// set difficulty factor for level. difficulty_factor can't be zero because
/// difficulty_factor can't be zero because /// Difficulty is calculated as:
/// Difficulty is calculated as
/// ```no_run /// ```no_run
/// let difficulty_factor = 500; /// let difficulty_factor = 500;
/// let difficulty = u128::max_value() - u128::max_value() / difficulty_factor; /// 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> { pub fn build(&mut self) -> CaptchaResult<Level> {
if self.visitor_count.is_none() { if self.visitor_threshold.is_none() {
Err(CaptchaError::SetVisitorCount) Err(CaptchaError::SetVisitorCount)
} else if self.difficulty_factor.is_none() { } else if self.difficulty_factor.is_none() {
Err(CaptchaError::SetDifficultyFactor) Err(CaptchaError::SetDifficultyFactor)
} else { } else {
Ok(Level { Ok(Level {
difficulty_factor: self.difficulty_factor.unwrap(), 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)] #[derive(Debug, Clone, PartialEq)]
pub struct Defense { pub struct Defense {
levels: Vec<Level>, levels: Vec<Level>,
@ -102,6 +122,7 @@ pub struct Defense {
current_visitor_threshold: usize, current_visitor_threshold: usize,
} }
/// Builder struct for [Defense]
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct DefenseBuilder { pub struct DefenseBuilder {
levels: Vec<Level>, levels: Vec<Level>,
@ -114,9 +135,10 @@ impl Default for DefenseBuilder {
} }
impl DefenseBuilder { impl DefenseBuilder {
/// add a level to [Defense]
pub fn add_level(&mut self, level: Level) -> CaptchaResult<&mut Self> { pub fn add_level(&mut self, level: Level) -> CaptchaResult<&mut Self> {
for i in self.levels.iter() { for i in self.levels.iter() {
if i.visitor_count == level.visitor_count { if i.visitor_threshold == level.visitor_threshold {
return Err(CaptchaError::DuplicateVisitorCount); return Err(CaptchaError::DuplicateVisitorCount);
} }
} }
@ -124,12 +146,11 @@ impl DefenseBuilder {
Ok(self) Ok(self)
} }
/// Build [Defense]
pub fn build(&mut self) -> CaptchaResult<Defense> { pub fn build(&mut self) -> CaptchaResult<Defense> {
if !self.levels.is_empty() { if !self.levels.is_empty() {
// sort levels to arrange in ascending order // sort levels to arrange in ascending order
self.levels.sort_by_key(|a| a.visitor_count); self.levels.sort_by_key(|a| a.visitor_threshold);
// as visitor count increases, difficulty_factor too should increse
for level in self.levels.iter() { for level in self.levels.iter() {
if level.difficulty_factor == 0 { 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 { for i in 0..self.levels.len() - 1 {
if self.levels[i].difficulty_factor > self.levels[i + 1].difficulty_factor { if self.levels[i].difficulty_factor > self.levels[i + 1].difficulty_factor {
return Err(CaptchaError::DecreaseingDifficultyFactor); return Err(CaptchaError::DecreaseingDifficultyFactor);
@ -163,44 +186,45 @@ impl Default for Defense {
} }
impl Defense { impl Defense {
///! Difficulty is calculated as ///! Difficulty is calculated as:
///! ```rust ///! ```rust
///! let difficulty = u128::max_value() - u128::max_value() / difficulty_factor; ///! 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 { pub fn get_difficulty(&self) -> u32 {
self.levels[self.current_visitor_threshold].difficulty_factor self.levels[self.current_visitor_threshold].difficulty_factor
} }
/// tighten up defense. Increases defense level by a factor of one /// tighten up defense. Increases defense level by a factor of one.
/// when defense is at max level, calling this method will have no effect /// When defense is at max level, calling this method will have no effect
pub fn tighten_up(&mut self) { pub fn tighten_up(&mut self) {
if self.current_visitor_threshold != self.levels.len() - 1 { if self.current_visitor_threshold != self.levels.len() - 1 {
self.current_visitor_threshold += 1; self.current_visitor_threshold += 1;
} }
} }
/// loosen up defense. Decreases defense level by a factor of one /// 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 /// When defense is at the lowest level, calling this method will have no effect.
pub fn loosen_up(&mut self) { pub fn loosen_up(&mut self) {
if self.current_visitor_threshold != 0 { if self.current_visitor_threshold != 0 {
self.current_visitor_threshold -= 1; self.current_visitor_threshold -= 1;
} }
} }
/// set defense to maximum level /// Set defense to maximum level
pub fn max_defense(&mut self) { pub fn max_defense(&mut self) {
self.current_visitor_threshold = self.levels.len() - 1; self.current_visitor_threshold = self.levels.len() - 1;
} }
/// set defense to minimum level /// Set defense to minimum level
pub fn min_defense(&mut self) { pub fn min_defense(&mut self) {
self.current_visitor_threshold = 0; self.current_visitor_threshold = 0;
} }
/// Get current level's visitor threshold
pub fn visitor_threshold(&self) -> u32 { 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() let level = LevelBuilder::default()
.difficulty_factor(1) .difficulty_factor(1)
.unwrap() .unwrap()
.visitor_count(0) .visitor_threshold(0)
.build() .build()
.unwrap(); .unwrap();
assert_eq!(level.visitor_count, 0); assert_eq!(level.visitor_threshold, 0);
assert_eq!(level.difficulty_factor, 1); assert_eq!(level.difficulty_factor, 1);
assert_eq!( assert_eq!(
@ -227,12 +251,12 @@ mod tests {
} }
#[test] #[test]
fn defense_builder_duplicate_visitor_count() { fn defense_builder_duplicate_visitor_threshold() {
let mut defense_builder = DefenseBuilder::default(); let mut defense_builder = DefenseBuilder::default();
let err = defense_builder let err = defense_builder
.add_level( .add_level(
LevelBuilder::default() LevelBuilder::default()
.visitor_count(50) .visitor_threshold(50)
.difficulty_factor(50) .difficulty_factor(50)
.unwrap() .unwrap()
.build() .build()
@ -241,7 +265,7 @@ mod tests {
.unwrap() .unwrap()
.add_level( .add_level(
LevelBuilder::default() LevelBuilder::default()
.visitor_count(50) .visitor_threshold(50)
.difficulty_factor(50) .difficulty_factor(50)
.unwrap() .unwrap()
.build() .build()
@ -256,7 +280,7 @@ mod tests {
let err = defense_builder let err = defense_builder
.add_level( .add_level(
LevelBuilder::default() LevelBuilder::default()
.visitor_count(50) .visitor_threshold(50)
.difficulty_factor(50) .difficulty_factor(50)
.unwrap() .unwrap()
.build() .build()
@ -265,7 +289,7 @@ mod tests {
.unwrap() .unwrap()
.add_level( .add_level(
LevelBuilder::default() LevelBuilder::default()
.visitor_count(500) .visitor_threshold(500)
.difficulty_factor(10) .difficulty_factor(10)
.unwrap() .unwrap()
.build() .build()
@ -280,7 +304,7 @@ mod tests {
DefenseBuilder::default() DefenseBuilder::default()
.add_level( .add_level(
LevelBuilder::default() LevelBuilder::default()
.visitor_count(50) .visitor_threshold(50)
.difficulty_factor(50) .difficulty_factor(50)
.unwrap() .unwrap()
.build() .build()
@ -289,7 +313,7 @@ mod tests {
.unwrap() .unwrap()
.add_level( .add_level(
LevelBuilder::default() LevelBuilder::default()
.visitor_count(500) .visitor_threshold(500)
.difficulty_factor(5000) .difficulty_factor(5000)
.unwrap() .unwrap()
.build() .build()
@ -298,7 +322,7 @@ mod tests {
.unwrap() .unwrap()
.add_level( .add_level(
LevelBuilder::default() LevelBuilder::default()
.visitor_count(5000) .visitor_threshold(5000)
.difficulty_factor(50000) .difficulty_factor(50000)
.unwrap() .unwrap()
.build() .build()
@ -307,7 +331,7 @@ mod tests {
.unwrap() .unwrap()
.add_level( .add_level(
LevelBuilder::default() LevelBuilder::default()
.visitor_count(50000) .visitor_threshold(50000)
.difficulty_factor(500000) .difficulty_factor(500000)
.unwrap() .unwrap()
.build() .build()
@ -316,7 +340,7 @@ mod tests {
.unwrap() .unwrap()
.add_level( .add_level(
LevelBuilder::default() LevelBuilder::default()
.visitor_count(500000) .visitor_threshold(500000)
.difficulty_factor(5000000) .difficulty_factor(5000000)
.unwrap() .unwrap()
.build() .build()
@ -395,28 +419,4 @@ mod tests {
defense.loosen_up(); defense.loosen_up();
assert_eq!(defense.get_difficulty(), 50); 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}; use derive_more::{Display, Error};
/// Errors that can occur when using /// Error datatype
#[derive(Debug, PartialEq, Display, Clone, Error)] #[derive(Debug, PartialEq, Display, Clone, Error)]
#[cfg(not(tarpaulin_include))] #[cfg(not(tarpaulin_include))]
pub enum CaptchaError { pub enum CaptchaError {
/// when configuring m_captcha, [DefenseBuilder][crate::new_levels::DefenseBuilder] must be passed atleast /// When configuring m_captcha, [DefenseBuilder][crate::defense::DefenseBuilder]
/// one `LevelConfig` if not this error will arise /// must be passed atleast one `LevelConfig` if not this error will arise
#[display(fmt = "LevelBuilder should have atleaset one level configured")] #[display(fmt = "LevelBuilder should have atleaset one level configured")]
LevelEmpty, LevelEmpty,
/// Visitor count must be an integer /// Visitor count must be a whole number(zero and above).
/// when configuring m_captcha, [LevelBuilder][crate::new_levels::LevelBuilder] difficulty_factor /// When configuring m_captcha, [LevelBuilder][crate::defense::LevelBuilder].
/// must be set to greater than zero. /// difficulty_factor must be set to greater than zero.
#[display(fmt = "difficulty factor must be greater than zero")] #[display(fmt = "difficulty factor must be greater than zero")]
DifficultyFactorZero, 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 counter;
pub mod defense;
pub mod errors; 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};