doc and better names
This commit is contained in:
parent
0356bb1329
commit
a884edf2f2
5 changed files with 320 additions and 143 deletions
24
Cargo.lock
generated
24
Cargo.lock
generated
|
@ -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"
|
||||
|
|
131
src/counter.rs
131
src/counter.rs
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
// }
|
||||
}
|
|
@ -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,
|
||||
|
||||
|
|
102
src/lib.rs
102
src/lib.rs
|
@ -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};
|
||||
|
|
Loading…
Reference in a new issue