diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
new file mode 100644
index 0000000..159c5cb
--- /dev/null
+++ b/.github/workflows/linux.yml
@@ -0,0 +1,80 @@
+name: CI (Linux)
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened]
+ push:
+ branches:
+ - master
+
+
+jobs:
+ build_and_test:
+ strategy:
+ fail-fast: false
+ matrix:
+ version:
+ - stable
+ - nightly
+
+ name: ${{ matrix.version }} - x86_64-unknown-linux-gnu
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: ⚡ Cache
+ uses: actions/cache@v2
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+
+ - name: Install ${{ matrix.version }}
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: ${{ matrix.version }}-x86_64-unknown-linux-gnu
+ profile: minimal
+ override: true
+
+ - name: check build
+ uses: actions-rs/cargo@v1
+ with:
+ command: check
+ args: --all --bins --examples --tests
+
+ - name: tests
+ uses: actions-rs/cargo@v1
+ timeout-minutes: 40
+ with:
+ command: test
+ args: --all --all-features --no-fail-fast
+
+ - name: Generate coverage file
+ if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
+ uses: actions-rs/tarpaulin@v0.1
+ with:
+ version: '0.15.0'
+ args: '-t 1200'
+
+ - name: Upload to Codecov
+ if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
+ uses: codecov/codecov-action@v1
+ with:
+ file: cobertura.xml
+
+ - name: generate documentation
+ if: matrix.version == 'stable' && (github.repository == 'realaravinth/mCaptcha')
+ uses: actions-rs/cargo@v1
+ with:
+ command: doc
+ args: --no-deps --workspace --all-features
+
+ - name: Deploy to GitHub Pages
+ if: matrix.version == 'stable' && (github.repository == 'realaravinth/mCaptcha')
+ uses: JamesIves/github-pages-deploy-action@3.7.1
+ with:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ BRANCH: gh-pages
+ FOLDER: target/doc
diff --git a/.gitignore b/.gitignore
index ea8c4bf..d5aabfe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
/target
+tarpaulin-report.html
diff --git a/Cargo.lock b/Cargo.lock
index c95f591..9f12b96 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -205,6 +205,66 @@ dependencies = [
"lazy_static",
]
+[[package]]
+name = "darling"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "derive_builder"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0"
+dependencies = [
+ "darling",
+ "derive_builder_core",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "derive_builder_core"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "derive_more"
version = "0.99.11"
@@ -371,6 +431,12 @@ dependencies = [
"winapi 0.3.9",
]
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
[[package]]
name = "idna"
version = "0.2.2"
@@ -487,7 +553,10 @@ name = "m_captcha"
version = "0.1.0"
dependencies = [
"actix",
+ "actix-rt",
+ "derive_builder",
"derive_more",
+ "lazy_static",
"serde",
"serde_json",
]
@@ -839,6 +908,12 @@ dependencies = [
"winapi 0.3.9",
]
+[[package]]
+name = "strsim"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
+
[[package]]
name = "syn"
version = "1.0.60"
diff --git a/Cargo.toml b/Cargo.toml
index 1ece608..8b31dd7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,6 +16,9 @@ members = [ ".", "browser", "cli" ]
[dependencies]
actix = "0.10"
-derive_more = "0.99"
serde = "1.0.114"
serde_json = "1"
+derive_builder = "0.9"
+derive_more = "0.99"
+lazy_static = "1.4"
+actix-rt = "1"
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b50529e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,28 @@
+
+
mCaptcha
+
+ mCaptcha - PoW based DoS protection
+
+
+[![Documentation](https://img.shields.io/badge/docs-master-blue)](https://realaravinth.github.io/mCaptcha/m_captcha/index.html)
+![CI (Linux)](
)
+[![dependency status](https://deps.rs/repo/github/realaravinth/mCaptcha/status.svg)](https://deps.rs/repo/github/realaravinth/mCaptcha)
+
+[![codecov](https://codecov.io/gh/realaravinth/mCaptcha/branch/master/graph/badge.svg)](https://codecov.io/gh/realaravinth/mCaptcha)
+
+
+
+## Features
+TODO
+
+## Usage:
+
+Add this to your `Cargo.toml`:
+
+```toml
+mCaptcha = { version = "0.1", git = "https://github.com/realaravinth/mCaptcha" }
+```
+
+## Examples:
+
+TODO
diff --git a/src/counter.rs b/src/counter.rs
new file mode 100644
index 0000000..42bc025
--- /dev/null
+++ b/src/counter.rs
@@ -0,0 +1,158 @@
+use std::time::Duration;
+
+use actix::prelude::*;
+use derive_builder::Builder;
+//use lazy_static::*;
+
+use crate::levels::Levels;
+
+// TODO move this into config parameter
+// lazy_static! {
+// pub static ref DURATION: Duration = Duration::new(POW_SESSION_DURATION, 0);
+// }
+
+/// Add visitor message
+#[derive(Message)]
+#[rtype(result = "u32")]
+pub struct Visitor;
+
+#[derive(Message)]
+#[rtype(result = "()")]
+struct DeleteVisitor;
+
+#[derive(Builder)]
+pub struct Counter {
+ visitor_count: usize,
+ levels: Levels,
+ duration: u64,
+}
+
+impl Default for Counter {
+ fn default() -> Self {
+ Counter {
+ visitor_count: 0,
+ levels: Levels::default(),
+ duration: 30,
+ }
+ }
+}
+
+impl Actor for Counter {
+ type Context = Context;
+
+ // fn started(&mut self, ctx: &mut Self::Context) {
+ // ctx.set_mailbox_capacity(usize::MAX / 2);
+ // }
+}
+
+impl Handler for Counter {
+ type Result = u32;
+ fn handle(&mut self, _: Visitor, ctx: &mut Self::Context) -> Self::Result {
+ use actix::clock::delay_for;
+
+ self.visitor_count += 1;
+
+ let addr = ctx.address();
+
+ let duration: Duration = Duration::new(self.duration.clone(), 0);
+ let wait_for = async move {
+ delay_for(duration).await;
+ addr.send(DeleteVisitor).await.unwrap();
+ }
+ .into_actor(self);
+ ctx.spawn(wait_for);
+
+ if self.visitor_count > self.levels.threshold() {
+ self.levels.focus();
+ } else {
+ self.levels.relax();
+ }
+
+ self.levels.get_difficulty()
+ }
+}
+
+impl Handler for Counter {
+ type Result = ();
+ fn handle(&mut self, _msg: DeleteVisitor, _ctx: &mut Self::Context) -> Self::Result {
+ if self.visitor_count > 0 {
+ self.visitor_count -= 1;
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ async fn race(addr: Addr, count: Levels) {
+ for _ in 0..count as usize - 1 {
+ let _ = addr.send(Visitor).await.unwrap();
+ }
+ }
+ #[actix_rt::test]
+ async fn counter_focus_works() {
+ let four = Levels::Four.get_difficulty();
+ let three = Levels::Three.get_difficulty();
+ let two = Levels::Two.get_difficulty();
+ let one = Levels::One.get_difficulty();
+
+ let addr = Counter::default().start();
+
+ let mut difficulty_factor = addr.send(Visitor).await.unwrap();
+ assert_eq!(difficulty_factor, one);
+
+ let addr = Counter::default().start();
+ race(addr.clone(), Levels::One).await;
+ difficulty_factor = addr.send(Visitor).await.unwrap();
+ assert_eq!(difficulty_factor, one);
+
+ let addr = Counter::default().start();
+ race(addr.clone(), Levels::Two).await;
+ addr.send(Visitor).await.unwrap();
+ difficulty_factor = addr.send(Visitor).await.unwrap();
+ assert_eq!(difficulty_factor, two);
+
+ let addr = Counter::default().start();
+ race(addr.clone(), Levels::Three).await;
+ difficulty_factor = addr.send(Visitor).await.unwrap();
+ assert_eq!(difficulty_factor, three);
+
+ let addr = Counter::default().start();
+ race(addr.clone(), Levels::Four).await;
+ addr.send(Visitor).await.unwrap();
+ difficulty_factor = addr.send(Visitor).await.unwrap();
+ assert_eq!(difficulty_factor, four);
+ }
+
+ #[actix_rt::test]
+ async fn counter_relax_works() {
+ use actix::clock::delay_for;
+ let four = Levels::Four.get_difficulty();
+ let three = Levels::Three.get_difficulty();
+ let two = Levels::Two.get_difficulty();
+ let one = Levels::One.get_difficulty();
+
+ let addr = Counter::default().start();
+
+ let mut difficulty_factor = addr.send(Visitor).await.unwrap();
+
+ let addr = Counter::default().start();
+ race(addr.clone(), Levels::Four).await;
+ addr.send(Visitor).await.unwrap();
+ difficulty_factor = addr.send(Visitor).await.unwrap();
+ assert_eq!(difficulty_factor, four);
+
+ // could break when default duration for counter actor changes
+ let duration = Duration::new(30, 0);
+
+ delay_for(duration).await;
+
+ difficulty_factor = addr.send(Visitor).await.unwrap();
+ assert_eq!(difficulty_factor, three);
+ difficulty_factor = addr.send(Visitor).await.unwrap();
+ assert_eq!(difficulty_factor, two);
+ difficulty_factor = addr.send(Visitor).await.unwrap();
+ assert_eq!(difficulty_factor, one);
+ }
+}
diff --git a/src/errors.rs b/src/errors.rs
new file mode 100644
index 0000000..b666fe7
--- /dev/null
+++ b/src/errors.rs
@@ -0,0 +1,34 @@
+//! Error datatypes
+use derive_more::{Display, Error};
+
+/// Errors that can occur when interacting with the blockchain
+#[derive(Debug, PartialEq, Display, Clone, Error)]
+#[cfg(not(tarpaulin_include))]
+pub enum CaptchaError {
+ /// when configuring m_captcha, `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` difficulty_factor
+ /// must be set to greater than zero.
+ #[display(fmt = "difficulty factor must be greater than zero")]
+ DifficultyFactorZero,
+ /// Difficulty factor must be set
+ #[display(fmt = "Set difficulty factor")]
+ SetDifficultyFactor,
+ /// Visitor count must be set
+ #[display(fmt = "Set visitor count")]
+ SetVisitorCount,
+
+ /// Visitor count must be Unique
+ #[display(fmt = "Duplicate visitor count")]
+ DuplicateVisitorCount,
+
+ /// Difficulty factor should increase with level
+ #[display(fmt = "Difficulty factor should increase with level")]
+ DecreaseingDifficultyFactor,
+}
+
+/// [Result] datatype for m_captcha
+pub type CaptchaResult = std::result::Result;
diff --git a/src/levels.rs b/src/levels.rs
new file mode 100644
index 0000000..64b5a55
--- /dev/null
+++ b/src/levels.rs
@@ -0,0 +1,162 @@
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub enum Levels {
+ One = 500,
+ Two = 1000,
+ Three = 2000,
+ Four = 4000,
+}
+
+impl Default for Levels {
+ fn default() -> Self {
+ Levels::One
+ }
+}
+
+impl Levels {
+ pub fn relax(&mut self) -> u32 {
+ self.rev().next();
+ *self as u32
+ }
+
+ ///! Difficulty is calculated as
+ ///! ```rust
+ ///! let difficulty = u128::max_value() - u128::max_value() / difficulty_factor;
+ ///! ```
+ ///! the higher the `difficulty_factor`, the higher the difficulty.
+
+ pub fn get_difficulty(&self) -> u32 {
+ match self {
+ Levels::Three => 100_000,
+ Levels::Four => 1_000_000,
+ _ => *self as u32,
+ }
+ }
+
+ pub fn threshold(&self) -> usize {
+ *self as usize
+ }
+
+ pub fn focus(&mut self) -> u32 {
+ self.next();
+ *self as u32
+ }
+}
+
+impl DoubleEndedIterator for Levels {
+ fn next_back(&mut self) -> Option {
+ match self {
+ Levels::One => {
+ *self = Levels::One;
+ }
+ Levels::Two => {
+ *self = Levels::One;
+ }
+ Levels::Three => {
+ *self = Levels::Two;
+ }
+ Levels::Four => {
+ *self = Levels::Three;
+ }
+ };
+
+ Some(())
+ }
+}
+
+impl Iterator for Levels {
+ type Item = ();
+
+ fn next(&mut self) -> Option<()> {
+ match self {
+ Levels::One => {
+ *self = Levels::Two;
+ }
+ Levels::Two => {
+ *self = Levels::Three;
+ }
+ Levels::Three => {
+ *self = Levels::Four;
+ }
+ Levels::Four => {
+ *self = Levels::Four;
+ }
+ };
+
+ Some(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn levels_enum_works() {
+ let mut level = Levels::default();
+
+ assert_eq!(level, Levels::One);
+
+ level.next();
+ assert_eq!(level, Levels::Two);
+
+ level.next();
+ assert_eq!(level, Levels::Three);
+
+ level.next();
+ assert_eq!(level, Levels::Four);
+
+ level.next();
+ assert_eq!(level, Levels::Four);
+ }
+
+ #[test]
+ fn focus_works() {
+ let mut level = Levels::default();
+
+ assert_eq!(level, Levels::One);
+ assert_eq!(level.focus(), Levels::Two as u32);
+ assert_eq!(level.focus(), Levels::Three as u32);
+ assert_eq!(level.focus(), Levels::Four as u32);
+ assert_eq!(level.focus(), Levels::Four as u32);
+ }
+
+ #[test]
+ fn relax_works() {
+ let mut level = Levels::default();
+
+ assert_eq!(level, Levels::One);
+ assert_eq!(level.focus(), Levels::Two as u32);
+ assert_eq!(level.focus(), Levels::Three as u32);
+ assert_eq!(level.focus(), Levels::Four as u32);
+
+ assert_eq!(level.relax(), Levels::Three as u32);
+ assert_eq!(level.relax(), Levels::Two as u32);
+
+ assert_eq!(level.relax(), Levels::One as u32);
+ assert_eq!(level.relax(), Levels::One as u32);
+ }
+
+ #[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/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..7951a15
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,4 @@
+pub mod counter;
+pub mod errors;
+mod levels;
+mod new_levels;
diff --git a/src/main.rs b/src/main.rs
deleted file mode 100644
index e7a11a9..0000000
--- a/src/main.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-fn main() {
- println!("Hello, world!");
-}
diff --git a/src/new_levels.rs b/src/new_levels.rs
new file mode 100644
index 0000000..cf57e8e
--- /dev/null
+++ b/src/new_levels.rs
@@ -0,0 +1,424 @@
+// //! ```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();
+// //! ```
+
+use crate::errors::*;
+
+/// Level struct
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub struct Level {
+ visitor_count: u32,
+ difficulty_factor: u32,
+}
+
+impl Default for Level {
+ fn default() -> Self {
+ Level {
+ visitor_count: 0,
+ difficulty_factor: 0,
+ }
+ }
+}
+
+/// set difficulty configuration
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub struct LevelBuilder {
+ visitor_count: Option,
+ difficulty_factor: Option,
+}
+
+impl Default for LevelBuilder {
+ fn default() -> Self {
+ LevelBuilder {
+ visitor_count: None,
+ difficulty_factor: None,
+ }
+ }
+}
+
+impl LevelBuilder {
+ /// set visitor count for level
+ pub fn visitor_count(&mut self, visitor_count: u32) -> &mut Self {
+ self.visitor_count = Some(visitor_count);
+ self
+ }
+
+ /// 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;
+ /// ```
+ /// the higher the `difficulty_factor`, the higher the difficulty.
+ pub fn difficulty_factor(&mut self, difficulty_factor: u32) -> CaptchaResult<&mut Self> {
+ if difficulty_factor > 0 {
+ self.difficulty_factor = Some(difficulty_factor);
+ Ok(self)
+ } else {
+ Err(CaptchaError::DifficultyFactorZero)
+ }
+ }
+
+ /// build Level
+ pub fn build(&mut self) -> CaptchaResult {
+ if self.visitor_count.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(),
+ })
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct Defense {
+ levels: Vec,
+ // index of current visitor threshold
+ current_visitor_threshold: usize,
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct DefenseBuilder {
+ levels: Vec,
+}
+
+impl Default for DefenseBuilder {
+ fn default() -> Self {
+ DefenseBuilder { levels: vec![] }
+ }
+}
+
+impl DefenseBuilder {
+ pub fn add_level(&mut self, level: Level) -> CaptchaResult<&mut Self> {
+ for i in self.levels.iter() {
+ if i.visitor_count == level.visitor_count {
+ return Err(CaptchaError::DuplicateVisitorCount);
+ }
+ }
+ self.levels.push(level);
+ Ok(self)
+ }
+
+ 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
+
+ for level in self.levels.iter() {
+ if level.difficulty_factor == 0 {
+ return Err(CaptchaError::DifficultyFactorZero);
+ }
+ }
+
+ for i in 0..self.levels.len() - 1 {
+ if self.levels[i].difficulty_factor > self.levels[i + 1].difficulty_factor {
+ return Err(CaptchaError::DecreaseingDifficultyFactor);
+ }
+ }
+
+ Ok(Defense {
+ levels: self.levels.to_owned(),
+ current_visitor_threshold: 0,
+ })
+ } else {
+ Err(CaptchaError::LevelEmpty)
+ }
+ }
+}
+
+impl Default for Defense {
+ fn default() -> Self {
+ Defense {
+ levels: vec![Level::default()],
+ current_visitor_threshold: 0,
+ }
+ }
+}
+
+impl Defense {
+ ///! Difficulty is calculated as
+ ///! ```rust
+ ///! let difficulty = u128::max_value() - u128::max_value() / difficulty_factor;
+ ///! ```
+ ///! the higher the `difficulty_factor`, the higher the difficulty.
+
+ /// 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
+ pub fn tighten_up(&mut self) {
+ println!("{}", self.current_visitor_threshold);
+ if self.current_visitor_threshold != self.levels.len() - 1 {
+ self.current_visitor_threshold += 1;
+ println!("{}", self.current_visitor_threshold);
+ }
+ }
+ /// 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
+ pub fn max_defense(&mut self) {
+ self.current_visitor_threshold = self.levels.len() - 1;
+ }
+
+ /// set defense to minimum level
+ pub fn min_defense(&mut self) {
+ self.current_visitor_threshold = 0;
+ }
+
+ pub fn visitor_threshold(&self) -> u32 {
+ self.levels[self.current_visitor_threshold].visitor_count
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn level_builder_works() {
+ let level = LevelBuilder::default()
+ .difficulty_factor(1)
+ .unwrap()
+ .visitor_count(0)
+ .build()
+ .unwrap();
+
+ assert_eq!(level.visitor_count, 0);
+ assert_eq!(level.difficulty_factor, 1);
+
+ assert_eq!(
+ LevelBuilder::default().difficulty_factor(0),
+ Err(CaptchaError::DifficultyFactorZero)
+ );
+ }
+
+ #[test]
+ fn defense_builder_duplicate_visitor_count() {
+ let mut defense_builder = DefenseBuilder::default();
+ let err = defense_builder
+ .add_level(
+ LevelBuilder::default()
+ .visitor_count(50)
+ .difficulty_factor(50)
+ .unwrap()
+ .build()
+ .unwrap(),
+ )
+ .unwrap()
+ .add_level(
+ LevelBuilder::default()
+ .visitor_count(50)
+ .difficulty_factor(50)
+ .unwrap()
+ .build()
+ .unwrap(),
+ );
+ assert_eq!(err, Err(CaptchaError::DuplicateVisitorCount));
+ }
+
+ #[test]
+ fn defense_builder_decreasing_difficulty_factor() {
+ let mut defense_builder = DefenseBuilder::default();
+ let err = defense_builder
+ .add_level(
+ LevelBuilder::default()
+ .visitor_count(50)
+ .difficulty_factor(50)
+ .unwrap()
+ .build()
+ .unwrap(),
+ )
+ .unwrap()
+ .add_level(
+ LevelBuilder::default()
+ .visitor_count(500)
+ .difficulty_factor(10)
+ .unwrap()
+ .build()
+ .unwrap(),
+ )
+ .unwrap()
+ .build();
+ assert_eq!(err, Err(CaptchaError::DecreaseingDifficultyFactor));
+ }
+
+ fn get_defense() -> Defense {
+ 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(5000)
+ .unwrap()
+ .build()
+ .unwrap(),
+ )
+ .unwrap()
+ .add_level(
+ LevelBuilder::default()
+ .visitor_count(5000)
+ .difficulty_factor(50000)
+ .unwrap()
+ .build()
+ .unwrap(),
+ )
+ .unwrap()
+ .add_level(
+ LevelBuilder::default()
+ .visitor_count(50000)
+ .difficulty_factor(500000)
+ .unwrap()
+ .build()
+ .unwrap(),
+ )
+ .unwrap()
+ .add_level(
+ LevelBuilder::default()
+ .visitor_count(500000)
+ .difficulty_factor(5000000)
+ .unwrap()
+ .build()
+ .unwrap(),
+ )
+ .unwrap()
+ .build()
+ .unwrap()
+ }
+ #[test]
+ fn defense_builder_works() {
+ let defense = get_defense();
+
+ assert_eq!(defense.levels[0].difficulty_factor, 50);
+ assert_eq!(defense.levels[1].difficulty_factor, 5000);
+ assert_eq!(defense.levels[2].difficulty_factor, 50_000);
+ assert_eq!(defense.levels[3].difficulty_factor, 500_000);
+ assert_eq!(defense.levels[4].difficulty_factor, 5_000_000);
+ }
+
+ #[test]
+ fn tighten_up_works() {
+ let mut defense = get_defense();
+
+ assert_eq!(defense.get_difficulty(), 50);
+
+ defense.tighten_up();
+ assert_eq!(defense.get_difficulty(), 5_000);
+
+ defense.tighten_up();
+ assert_eq!(defense.get_difficulty(), 50_000);
+
+ defense.tighten_up();
+ assert_eq!(defense.get_difficulty(), 500_000);
+
+ defense.tighten_up();
+ assert_eq!(defense.get_difficulty(), 5_000_000);
+
+ defense.tighten_up();
+ assert_eq!(defense.get_difficulty(), 5_000_000);
+ }
+
+ #[test]
+ fn max_defense_works() {
+ let mut defense = get_defense();
+ defense.max_defense();
+ assert_eq!(defense.get_difficulty(), 5_000_000);
+ }
+
+ #[test]
+ fn minimum_defense_works() {
+ let mut defense = get_defense();
+ defense.min_defense();
+ assert_eq!(defense.get_difficulty(), 50);
+ }
+
+ #[test]
+ fn loosen_up_works() {
+ let mut defense = get_defense();
+ defense.max_defense();
+
+ assert_eq!(defense.get_difficulty(), 5_000_000);
+
+ defense.loosen_up();
+ assert_eq!(defense.get_difficulty(), 500_000);
+
+ defense.loosen_up();
+ assert_eq!(defense.get_difficulty(), 50_000);
+
+ defense.loosen_up();
+ assert_eq!(defense.get_difficulty(), 5_000);
+
+ defense.loosen_up();
+ assert_eq!(defense.get_difficulty(), 50);
+
+ 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);
+ // }
+}