counter is now a wrapper over mcaptcha

This commit is contained in:
Aravinth Manivannan 2021-06-05 19:55:35 +05:30
parent cc88c69fc6
commit 13b38c5b6c
Signed by: realaravinth
GPG key ID: AD9F0F08E855ED88
8 changed files with 508 additions and 125 deletions

View file

@ -15,11 +15,8 @@
- `master::Master` is moved to `master::embedded::master` in preparation
for Redis based implementation.
- `crate::mcaptcha` is moved to `master::embedded::counter` in preparation
for Redis based implementation.
- `AddSite` message for `Master` now requires an instance of
`crate::master::MCaptcha`. In the case of
`crate::mcaptcha::MCaptcha`. In the case of
`crate::master::embedded::master`, it automatically starts `Counter`
actor.
@ -54,7 +51,7 @@
## Changed
- actix upgraded to `0.11`
- `actix` upgraded to `0.11`
## 0.1.1

143
Cargo.lock generated
View file

@ -9,7 +9,7 @@ dependencies = [
"actix-rt",
"actix_derive",
"bitflags",
"bytes",
"bytes 0.5.6",
"crossbeam-channel",
"derive_more",
"futures-channel",
@ -19,8 +19,8 @@ dependencies = [
"parking_lot",
"pin-project",
"smallvec",
"tokio",
"tokio-util",
"tokio 0.2.25",
"tokio-util 0.3.1",
"trust-dns-proto",
"trust-dns-resolver",
]
@ -47,7 +47,7 @@ dependencies = [
"futures-channel",
"futures-util",
"smallvec",
"tokio",
"tokio 0.2.25",
]
[[package]]
@ -150,6 +150,12 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
[[package]]
name = "bytes"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
[[package]]
name = "cfg-if"
version = "0.1.10"
@ -162,6 +168,19 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "combine"
version = "4.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc4369b5e4c0cddf64ad8981c0111e7df4f7078f4d6ba98fb31f2e17c4c57b7e"
dependencies = [
"bytes 1.0.1",
"futures-util",
"memchr",
"pin-project-lite 0.2.6",
"tokio 1.6.1",
]
[[package]]
name = "convert_case"
version = "0.4.0"
@ -180,6 +199,12 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
[[package]]
name = "crc16"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "338089f42c427b86394a5ee60ff321da23a5c89c9d89514c829687b26359fcff"
[[package]]
name = "crossbeam-channel"
version = "0.4.4"
@ -349,6 +374,12 @@ dependencies = [
"generic-array",
]
[[package]]
name = "dtoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
[[package]]
name = "enum-as-inner"
version = "0.3.3"
@ -628,6 +659,7 @@ dependencies = [
"pow_sha256",
"pretty_env_logger",
"rand 0.8.3",
"redis",
"serde",
"serde_json",
]
@ -702,12 +734,25 @@ dependencies = [
"kernel32-sys",
"libc",
"log",
"miow",
"miow 0.2.2",
"net2",
"slab",
"winapi 0.2.8",
]
[[package]]
name = "mio"
version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956"
dependencies = [
"libc",
"log",
"miow 0.3.7",
"ntapi",
"winapi 0.3.9",
]
[[package]]
name = "mio-uds"
version = "0.6.8"
@ -716,7 +761,7 @@ checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0"
dependencies = [
"iovec",
"libc",
"mio",
"mio 0.6.23",
]
[[package]]
@ -731,6 +776,15 @@ dependencies = [
"ws2_32-sys",
]
[[package]]
name = "miow"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "net2"
version = "0.2.37"
@ -742,6 +796,15 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "ntapi"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "num_cpus"
version = "1.13.0"
@ -965,6 +1028,28 @@ dependencies = [
"rand_core 0.6.2",
]
[[package]]
name = "redis"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a32cb439c4e89c1e6415e5b3b23d9d8cc6dc1bf5a8cade19adbd5418de803be"
dependencies = [
"async-trait",
"bytes 1.0.1",
"combine",
"crc16",
"dtoa",
"futures-util",
"itoa",
"percent-encoding",
"pin-project-lite 0.2.6",
"rand 0.8.3",
"sha1",
"tokio 1.6.1",
"tokio-util 0.6.7",
"url",
]
[[package]]
name = "redox_syscall"
version = "0.2.5"
@ -1044,6 +1129,12 @@ dependencies = [
"serde",
]
[[package]]
name = "sha1"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
[[package]]
name = "sha2"
version = "0.9.3"
@ -1171,14 +1262,14 @@ version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092"
dependencies = [
"bytes",
"bytes 0.5.6",
"fnv",
"futures-core",
"iovec",
"lazy_static",
"libc",
"memchr",
"mio",
"mio 0.6.23",
"mio-uds",
"pin-project-lite 0.1.12",
"signal-hook-registry",
@ -1186,19 +1277,47 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "tokio"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a38d31d7831c6ed7aad00aa4c12d9375fd225a6dd77da1d25b707346319a975"
dependencies = [
"autocfg",
"bytes 1.0.1",
"libc",
"memchr",
"mio 0.7.11",
"pin-project-lite 0.2.6",
]
[[package]]
name = "tokio-util"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
dependencies = [
"bytes",
"bytes 0.5.6",
"futures-core",
"futures-io",
"futures-sink",
"log",
"pin-project-lite 0.1.12",
"tokio",
"tokio 0.2.25",
]
[[package]]
name = "tokio-util"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592"
dependencies = [
"bytes 1.0.1",
"futures-core",
"futures-sink",
"log",
"pin-project-lite 0.2.6",
"tokio 1.6.1",
]
[[package]]
@ -1217,7 +1336,7 @@ dependencies = [
"rand 0.7.3",
"smallvec",
"thiserror",
"tokio",
"tokio 0.2.25",
"url",
]
@ -1236,7 +1355,7 @@ dependencies = [
"resolv-conf",
"smallvec",
"thiserror",
"tokio",
"tokio 0.2.25",
"trust-dns-proto",
]

View file

@ -24,6 +24,7 @@ derive_more = "0.99"
rand = "0.8"
pow_sha256 = { version = "0.2.1", git = "https://github.com/mcaptcha/pow_sha256" }
redis = { version = "0.20.1", features = ["tokio-comp","aio", "cluster"] }
[dev-dependencies]
actix-rt = "1"

View file

@ -189,6 +189,7 @@ pub mod master;
/// message datatypes to interact with [MCaptcha] actor
pub mod cache;
pub mod mcaptcha;
pub mod pow;
pub mod system;
mod utils;
@ -197,4 +198,4 @@ pub use crate::cache::hashcache::HashCache;
pub use defense::{Defense, DefenseBuilder, LevelBuilder};
pub use master::embedded::counter::Counter;
pub use master::MCaptchaBuilder;
pub use mcaptcha::{MCaptcha, MCaptchaBuilder};

View file

@ -21,7 +21,7 @@
//! ```rust
//! use libmcaptcha::{
//! master::embedded::counter::{Counter, AddVisitor},
//! master::MCaptchaBuilder,
//! MCaptchaBuilder,
//! cache::HashCache,
//! LevelBuilder,
//! DefenseBuilder
@ -86,56 +86,49 @@ use std::time::Duration;
use actix::dev::*;
use serde::{Deserialize, Serialize};
use crate::master::MCaptcha;
use crate::{defense::Defense, master::AddVisitorResult};
use crate::master::AddVisitorResult;
use crate::mcaptcha::MCaptcha;
/// This struct represents the mCaptcha state and is used
/// to configure leaky-bucket lifetime and manage defense
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Counter {
visitor_threshold: u32,
defense: Defense,
duration: u64,
}
pub struct Counter(MCaptcha);
impl From<MCaptcha> for Counter {
fn from(m: MCaptcha) -> Counter {
let m = Counter {
duration: m.duration,
defense: m.defense,
visitor_threshold: m.visitor_threshold,
};
m
Counter(m)
}
}
impl Counter {
/// increments the visitor count by one
pub fn add_visitor(&mut self) {
self.visitor_threshold += 1;
if self.visitor_threshold > self.defense.visitor_threshold() {
self.defense.tighten_up();
} else {
self.defense.loosen_up();
}
}
// impl Counter {
// /// increments the visitor count by one
// pub fn add_visitor(&mut self) {
// self.visitor_threshold += 1;
// if self.visitor_threshold > self.defense.visitor_threshold() {
// self.defense.tighten_up();
// } else {
// self.defense.loosen_up();
// }
// }
//
// /// decrements the visitor count by one
// pub fn decrement_visitor(&mut self) {
// if self.visitor_threshold > 0 {
// self.visitor_threshold -= 1;
// }
// }
//
// /// get current difficulty factor
// pub fn get_difficulty(&self) -> u32 {
// self.defense.get_difficulty()
// }
//
// /// get [Counter]'s lifetime
// pub fn get_duration(&self) -> u64 {
// self.duration
// }
// }
/// decrements the visitor count by one
pub fn decrement_visitor(&mut self) {
if self.visitor_threshold > 0 {
self.visitor_threshold -= 1;
}
}
/// get current difficulty factor
pub fn get_difficulty(&self) -> u32 {
self.defense.get_difficulty()
}
/// get [Counter]'s lifetime
pub fn get_duration(&self) -> u64 {
self.duration
}
}
impl Actor for Counter {
type Context = Context<Self>;
}
@ -148,7 +141,7 @@ struct DeleteVisitor;
impl Handler<DeleteVisitor> for Counter {
type Result = ();
fn handle(&mut self, _msg: DeleteVisitor, _ctx: &mut Self::Context) -> Self::Result {
self.decrement_visitor();
self.0.decrement_visitor();
}
}
@ -161,8 +154,8 @@ pub struct AddVisitor;
impl AddVisitorResult {
fn new(m: &Counter) -> Self {
AddVisitorResult {
duration: m.get_duration(),
difficulty_factor: m.get_difficulty(),
duration: m.0.get_duration(),
difficulty_factor: m.0.get_difficulty(),
}
}
}
@ -174,7 +167,7 @@ impl Handler<AddVisitor> for Counter {
let addr = ctx.address();
use actix::clock::delay_for;
let duration: Duration = Duration::new(self.duration.clone(), 0);
let duration: Duration = Duration::new(self.0.get_duration(), 0);
let wait_for = async move {
//sleep(duration).await;
delay_for(duration).await;
@ -183,7 +176,7 @@ impl Handler<AddVisitor> for Counter {
.into_actor(self);
ctx.spawn(wait_for);
self.add_visitor();
self.0.add_visitor();
MessageResult(AddVisitorResult::new(&self))
}
}
@ -197,7 +190,7 @@ impl Handler<GetCurrentVisitorCount> for Counter {
type Result = MessageResult<GetCurrentVisitorCount>;
fn handle(&mut self, _: GetCurrentVisitorCount, _ctx: &mut Self::Context) -> Self::Result {
MessageResult(self.visitor_threshold)
MessageResult(self.0.get_visitors())
}
}

View file

@ -23,9 +23,14 @@ use derive_builder::Builder;
use serde::{Deserialize, Serialize};
pub mod embedded;
use crate::defense::Defense;
use crate::errors::*;
#[allow(
unused_variables,
unused_imports,
unused_variables,
dead_code,
unused_macros
)]
use crate::mcaptcha::*;
/// Describes actor handler trait impls that are required by a cache implementation
pub trait Master: actix::Actor + actix::Handler<AddVisitor> + actix::Handler<AddSite> {}
@ -53,60 +58,3 @@ pub struct AddSite {
pub id: String,
pub mcaptcha: MCaptcha,
}
/// Builder for [MCaptcha]
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct MCaptchaBuilder {
visitor_threshold: u32,
defense: Option<Defense>,
duration: Option<u64>,
}
impl Default for MCaptchaBuilder {
fn default() -> Self {
MCaptchaBuilder {
visitor_threshold: 0,
defense: None,
duration: None,
}
}
}
impl MCaptchaBuilder {
/// set defense
pub fn defense(&mut self, d: Defense) -> &mut Self {
self.defense = Some(d);
self
}
/// set duration
pub fn duration(&mut self, d: u64) -> &mut Self {
self.duration = Some(d);
self
}
/// Builds new [MCaptcha]
pub fn build(self: &mut MCaptchaBuilder) -> CaptchaResult<MCaptcha> {
if self.duration.is_none() {
Err(CaptchaError::PleaseSetValue("duration".into()))
} else if self.defense.is_none() {
Err(CaptchaError::PleaseSetValue("defense".into()))
} else if self.duration <= Some(0) {
Err(CaptchaError::CaptchaDurationZero)
} else {
let m = MCaptcha {
duration: self.duration.unwrap(),
defense: self.defense.clone().unwrap(),
visitor_threshold: self.visitor_threshold,
};
Ok(m)
}
}
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct MCaptcha {
visitor_threshold: u32,
defense: Defense,
duration: u64,
}

View file

@ -0,0 +1,206 @@
/*
* 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/>.
*/
use actix::dev::*;
use redis::cluster::ClusterClient;
//use redis::cluster::ClusterConnection;
use redis::Client;
//use redis::Connection;
use redis::RedisResult;
use redis::{aio::Connection, cluster::ClusterConnection};
//use crate::errors::*;
use crate::master::{AddSite, AddVisitor, Master as MasterTrait};
#[derive(Clone)]
pub enum Redis {
Single(Client),
Cluster(ClusterClient),
}
pub enum RedisConnection {
Single(Connection),
Cluster(ClusterConnection),
}
const INCR: &str = "MCAPTCHA_CACHE.COUNT";
const GET: &str = "MCAPTCHA_CACHE.GET";
#[derive(Clone)]
pub struct Master {
pub redis: Redis,
}
macro_rules! exec {
($cmd:expr, $con:expr) => {
match $con {
RedisConnection::Single(mut con) => $cmd.query_async(&mut con).await,
RedisConnection::Cluster(mut con) => $cmd.query(&mut con),
}
};
}
impl Master {
async fn add_visitor(&mut self, key: &str) {
let mut cmd = redis::cmd(INCR);
cmd.arg(&[key]);
let a: RedisResult<usize> = exec!(cmd, self.get_connection().await);
unimplemented!("Have to check return types of INCR command")
}
async fn get_visitors(&mut self, key: &str) {
let mut cmd = redis::cmd(GET);
cmd.arg(&[key]);
let a: RedisResult<usize> = exec!(cmd, self.get_connection().await);
unimplemented!("Have to check return types of GET command")
}
async fn get_connection(&mut self) -> RedisConnection {
match &self.redis {
Redis::Single(c) => {
let con = c.get_async_connection().await.unwrap();
RedisConnection::Single(con)
}
Redis::Cluster(c) => {
let con = c.get_connection().unwrap();
RedisConnection::Cluster(con)
}
}
}
async fn module_is_loaded(&mut self) -> () {
let mut cmd = redis::cmd("COMMAND");
cmd.arg(&["INFO", INCR]);
let a: RedisResult<usize> = exec!(cmd, self.get_connection().await);
let mut cmd = redis::cmd("COMMAND");
cmd.arg(&["INFO", GET]);
let a: RedisResult<usize> = exec!(cmd, self.get_connection().await);
unimplemented!("Have to check return types of INFO command")
}
}
impl MasterTrait for Master {}
impl Actor for Master {
type Context = Context<Self>;
}
impl Handler<AddVisitor> for Master {
type Result = MessageResult<AddVisitor>;
fn handle(&mut self, m: AddVisitor, ctx: &mut Self::Context) -> Self::Result {
let fut = async {
self.add_visitor(&m.0).await;
};
unimplemented!();
}
}
impl Handler<AddSite> for Master {
type Result = ();
fn handle(&mut self, m: AddSite, _ctx: &mut Self::Context) -> Self::Result {
unimplemented!();
}
}
//#[derive(Clone)]
//pub struct Cluster(pub ClusterClient);
//#[derive(Clone)]
//pub struct Single(pub Client);
//
//pub trait Redis: 'static + Unpin + Clone {
// type Result;
// fn get_connection(&'static self) -> Self::Result;
//}
//impl Redis for Cluster {
// type Result = RedisResult<ClusterConnection>;
// fn get_connection(&self) -> Self::Result {
// self.0.get_connection()
// }
//}
//
//impl Redis for Single {
// type Result = impl Future;
// fn get_connection(&'static self) -> Self::Result {
// self.0.get_async_connection()
// }
//}
//#[derive(Clone)]
//pub struct Master {
// pub redis: usize,
//}
//impl MasterTrait for Master {}
//
//impl Actor for Master {
// type Context = Context<Self>;
//}
//
//impl Handler<AddVisitor> for Master {
// type Result = MessageResult<AddVisitor>;
//
// fn handle(&mut self, m: AddVisitor, ctx: &mut Self::Context) -> Self::Result {
// let fut = async {
// let test = "1";
// };
// unimplemented!();
// }
//}
//
//impl Handler<AddSite> for Master {
// type Result = ();
//
// fn handle(&mut self, m: AddSite, _ctx: &mut Self::Context) -> Self::Result {
// unimplemented!();
// }
//}
//pub struct Master<T: Redis> {
// pub redis: T,
//}
//
//impl<T: Redis> MasterTrait for Master<T> {}
//
//impl<T: Redis> Actor for Master<T> {
// type Context = Context<Self>;
//}
//
//impl<T: Redis> Handler<AddVisitor> for Master<T> {
// type Result = MessageResult<AddVisitor>;
//
// fn handle(&mut self, m: AddVisitor, ctx: &mut Self::Context) -> Self::Result {
// let fut = async {
// self.redis.get_connection();
// let test = "1";
// };
// unimplemented!();
// }
//}
//
//impl<T: Redis> Handler<AddSite> for Master<T> {
// type Result = ();
//
// fn handle(&mut self, m: AddSite, _ctx: &mut Self::Context) -> Self::Result {
// unimplemented!();
// }
//}

118
src/mcaptcha.rs Normal file
View file

@ -0,0 +1,118 @@
/*
* 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/>.
*/
use serde::{Deserialize, Serialize};
use crate::defense::Defense;
use crate::errors::*;
/// Builder for [MCaptcha]
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct MCaptchaBuilder {
visitor_threshold: u32,
defense: Option<Defense>,
duration: Option<u64>,
}
impl Default for MCaptchaBuilder {
fn default() -> Self {
MCaptchaBuilder {
visitor_threshold: 0,
defense: None,
duration: None,
}
}
}
impl MCaptchaBuilder {
/// set defense
pub fn defense(&mut self, d: Defense) -> &mut Self {
self.defense = Some(d);
self
}
/// set duration
pub fn duration(&mut self, d: u64) -> &mut Self {
self.duration = Some(d);
self
}
/// Builds new [MCaptcha]
pub fn build(self: &mut MCaptchaBuilder) -> CaptchaResult<MCaptcha> {
if self.duration.is_none() {
Err(CaptchaError::PleaseSetValue("duration".into()))
} else if self.defense.is_none() {
Err(CaptchaError::PleaseSetValue("defense".into()))
} else if self.duration <= Some(0) {
Err(CaptchaError::CaptchaDurationZero)
} else {
let m = MCaptcha {
duration: self.duration.unwrap(),
defense: self.defense.clone().unwrap(),
visitor_threshold: self.visitor_threshold,
};
Ok(m)
}
}
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct MCaptcha {
visitor_threshold: u32,
defense: Defense,
duration: u64,
}
impl MCaptcha {
/// increments the visitor count by one
#[inline]
pub fn add_visitor(&mut self) {
self.visitor_threshold += 1;
if self.visitor_threshold > self.defense.visitor_threshold() {
self.defense.tighten_up();
} else {
self.defense.loosen_up();
}
}
/// decrements the visitor count by one
#[inline]
pub fn decrement_visitor(&mut self) {
if self.visitor_threshold > 0 {
self.visitor_threshold -= 1;
}
}
/// get current difficulty factor
#[inline]
pub fn get_difficulty(&self) -> u32 {
self.defense.get_difficulty()
}
/// get [Counter]'s lifetime
#[inline]
pub fn get_duration(&self) -> u64 {
self.duration
}
/// get [Counter]'s current visitor_threshold
#[inline]
pub fn get_visitors(&self) -> u32 {
self.visitor_threshold
}
}