in-memory pow config cache

This commit is contained in:
Aravinth Manivannan 2021-03-05 21:52:41 +05:30
parent 94f224ef0f
commit 8ddaf4ec2e
Signed by: realaravinth
GPG key ID: AD9F0F08E855ED88
6 changed files with 287 additions and 40 deletions

11
Cargo.lock generated
View file

@ -104,9 +104,9 @@ dependencies = [
[[package]]
name = "async-trait"
version = "0.1.42"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d"
checksum = "3a4c64e223db1fffa7683a719921434caa880463cfa5820032b063c9ecd5cc49"
dependencies = [
"proc-macro2",
"quote",
@ -684,9 +684,12 @@ version = "0.1.0"
dependencies = [
"actix",
"actix-rt",
"async-trait",
"derive_builder",
"derive_more",
"lazy_static",
"pow_sha256",
"rand",
"serde",
"serde_json",
]
@ -1145,9 +1148,9 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
[[package]]
name = "syn"
version = "1.0.60"
version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
checksum = "ed22b90a0e734a23a7610f4283ac9e5acfb96cbb30dfefa540d66f866f1c09c5"
dependencies = [
"proc-macro2",
"quote",

View file

@ -16,9 +16,17 @@ members = [ ".", "browser", "cli" ]
[dependencies]
actix = "0.10"
actix-rt = "1"
serde = "1.0.114"
serde_json = "1"
derive_builder = "0.9"
derive_more = "0.99"
lazy_static = "1.4"
actix-rt = "1"
rand = "0.7"
pow_sha256 = "0.2.1"
async-trait = "0.1.44"

View file

@ -19,7 +19,7 @@
//!
//! ## Usage:
//! ```rust
//! use m_captcha::{message::Visitor,MCaptchaBuilder, LevelBuilder, DefenseBuilder};
//! use m_captcha::{message::Visitor, MCaptchaBuilder, cache::HashCache, LevelBuilder, DefenseBuilder};
//! // traits from actix needs to be in scope for starting actor
//! use actix::prelude::*;
//!
@ -56,10 +56,12 @@
//! .unwrap();
//!
//! // create and start MCaptcha actor
//! let cache = HashCache::default().start();
//! let mcaptcha = MCaptchaBuilder::default()
//! .defense(defense)
//! // leaky bucket algorithm's emission interval
//! .duration(30)
//! .cache(cache)
//! .build()
//! .unwrap()
//! .start();
@ -71,34 +73,68 @@
//! }
//! ```
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use actix::dev::*;
use actix::prelude::*;
use async_trait::async_trait;
use derive_builder::Builder;
use pow_sha256::PoW as ShaPoW;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use serde::{Deserialize, Serialize};
use crate::defense::Defense;
use crate::errors::*;
use crate::hashcache::*;
/// Message to increment the visitor count
#[derive(Message)]
#[rtype(result = "u32")]
pub struct Visitor;
//#[async_trait]
//pub trait PersistPow {
// async fn save(&mut self, config: Arc<PoWConfig>) -> CaptchaResult<()>;
// async fn retrive(&mut self, string: &str) -> CaptchaResult<Option<u32>>;
//}
/// Message to decrement the visitor count
#[derive(Message)]
#[rtype(result = "()")]
struct DeleteVisitor;
pub trait Save: actix::Actor + actix::Handler<Retrive> + actix::Handler<Cache> {}
/// This struct represents the mCaptcha state and is used
/// to configure leaky-bucket lifetime and manage defense
#[derive(Builder)]
pub struct MCaptcha {
#[derive(Clone, Builder)]
pub struct MCaptcha<T>
where
// Actor + Handler<Cache>,
T: Save,
<T as Actor>::Context: ToEnvelope<T, Retrive> + ToEnvelope<T, Cache>,
// <T as Actor>::Context: ToEnvelope<T, Cache>,
{
#[builder(default = "0", setter(skip))]
visitor_threshold: u32,
defense: Defense,
duration: u64,
cache: Addr<T>,
}
impl MCaptcha {
//#[async_trait]
//impl PersistPow for HashCache {
// async fn save(&mut self, config: Arc<PoWConfig>) -> CaptchaResult<()> {
// self.insert(config.string.clone(), config.difficulty_factor);
// Ok(())
// }
//
// async fn retrive(&mut self, string: &str) -> CaptchaResult<Option<u32>> {
// if let Some(difficulty_factor) = self.get(string) {
// Ok(Some(difficulty_factor.to_owned()))
// } else {
// Ok(None)
// }
// }
//}
impl<T> MCaptcha<T>
where
T: Save,
<T as Actor>::Context: ToEnvelope<T, Retrive> + ToEnvelope<T, Cache>,
{
/// incerment visiotr count by one
pub fn add_visitor(&mut self) {
self.visitor_threshold += 1;
@ -120,16 +156,79 @@ impl MCaptcha {
pub fn get_difficulty(&self) -> u32 {
self.defense.get_difficulty()
}
}
impl Actor for MCaptcha {
// /// cache PoW configuration: difficulty and string
// pub async fn cache_pow(&mut self, pow: Arc<PoWConfig>) -> CaptchaResult<()> {
// unimplemented!();
// Ok(self.cache.save(pow).await?)
// }
//
// /// retrivee PoW configuration: difficulty and string
// pub async fn retrive_pow(&mut self, pow: &PoWConfig) -> CaptchaResult<Option<u32>> {
// unimplemented!();
// Ok(self.cache.retrive(&pow.string).await?)
// }
}
impl<T> Actor for MCaptcha<T>
where
T: Save,
<T as Actor>::Context: ToEnvelope<T, Retrive> + ToEnvelope<T, Cache>,
{
type Context = Context<Self>;
}
impl Handler<Visitor> for MCaptcha {
type Result = u32;
/// Message to decrement the visitor count
#[derive(Message)]
#[rtype(result = "()")]
struct DeleteVisitor;
impl<T> Handler<DeleteVisitor> for MCaptcha<T>
where
T: Save,
<T as Actor>::Context: ToEnvelope<T, Retrive> + ToEnvelope<T, Cache>,
{
type Result = ();
fn handle(&mut self, _msg: DeleteVisitor, _ctx: &mut Self::Context) -> Self::Result {
self.decrement_visiotr();
}
}
/// PoW Config that will be sent to clients for generating PoW
#[derive(Clone, Serialize, Debug)]
pub struct PoWConfig {
pub string: String,
pub difficulty_factor: u32,
}
impl PoWConfig {
pub fn new<T>(m: &MCaptcha<T>) -> Self
where
T: Save,
<T as Actor>::Context: ToEnvelope<T, Retrive> + ToEnvelope<T, Cache>,
{
PoWConfig {
string: thread_rng().sample_iter(&Alphanumeric).take(32).collect(),
difficulty_factor: m.get_difficulty(),
}
}
}
/// Message to increment the visitor count
#[derive(Message)]
#[rtype(result = "CaptchaResult<PoWConfig>")]
//#[rtype(result = "()")]
pub struct Visitor;
impl<T> Handler<Visitor> for MCaptcha<T>
where
T: Save,
<T as Actor>::Context: ToEnvelope<T, Retrive> + ToEnvelope<T, Cache>,
{
type Result = ResponseActFuture<Self, CaptchaResult<PoWConfig>>;
fn handle(&mut self, _: Visitor, ctx: &mut Self::Context) -> Self::Result {
use crate::hashcache::Cache;
use actix::clock::delay_for;
use actix::fut::wrap_future;
let addr = ctx.address();
@ -142,13 +241,34 @@ impl Handler<Visitor> for MCaptcha {
ctx.spawn(wait_for);
self.add_visitor();
self.get_difficulty()
let res = Arc::new(PoWConfig::new(&self));
let act_fut = wrap_future::<_, Self>(self.cache.send(Cache(res.clone()))).map(
|result, _actor, _ctx| match result {
Ok(Ok(())) => Ok(Arc::try_unwrap(res).unwrap()),
Ok(Err(e)) => Err(e),
Err(_) => Err(CaptchaError::MailboxError), //TODO do typecasting from mailbox error to captcha error
},
);
Box::pin(act_fut)
}
}
impl Handler<DeleteVisitor> for MCaptcha {
/// Message to decrement the visitor count
#[derive(Message, Deserialize)]
#[rtype(result = "()")]
pub struct VerifyPoW {
pow: ShaPoW<Vec<u8>>,
id: String,
}
impl<T> Handler<VerifyPoW> for MCaptcha<T>
where
T: Save,
<T as Actor>::Context: ToEnvelope<T, Retrive> + ToEnvelope<T, Cache>,
{
type Result = ();
fn handle(&mut self, _msg: DeleteVisitor, _ctx: &mut Self::Context) -> Self::Result {
fn handle(&mut self, msg: VerifyPoW, _ctx: &mut Self::Context) -> Self::Result {
self.decrement_visiotr();
}
}
@ -164,6 +284,9 @@ mod tests {
const LEVEL_2: (u32, u32) = (500, 500);
const DURATION: u64 = 10;
type MyActor = Addr<MCaptcha<HashCache>>;
type CacheAddr = Addr<HashCache>;
fn get_defense() -> Defense {
DefenseBuilder::default()
.add_level(
@ -188,15 +311,23 @@ mod tests {
.unwrap()
}
async fn race(addr: Addr<MCaptcha>, count: (u32, u32)) {
async fn race<T>(addr: Addr<MCaptcha<T>>, count: (u32, u32))
where
// Actor + Handler<Cache>,
T: Save,
<T as Actor>::Context: ToEnvelope<T, Retrive> + ToEnvelope<T, Cache>,
{
for _ in 0..count.0 as usize - 1 {
let _ = addr.send(Visitor).await.unwrap();
}
}
fn get_counter() -> MCaptcha {
fn get_counter() -> MCaptcha<crate::cache::HashCache> {
use actix::prelude::*;
let cache: CacheAddr = HashCache::default().start();
MCaptchaBuilder::default()
.defense(get_defense())
.cache(cache)
.duration(DURATION)
.build()
.unwrap()
@ -204,30 +335,30 @@ mod tests {
#[actix_rt::test]
async fn counter_defense_tightenup_works() {
let addr = get_counter().start();
let addr: MyActor = get_counter().start();
let mut difficulty_factor = addr.send(Visitor).await.unwrap();
assert_eq!(difficulty_factor, LEVEL_1.0);
let mut difficulty_factor = addr.send(Visitor).await.unwrap().unwrap();
assert_eq!(difficulty_factor.difficulty_factor, LEVEL_1.0);
race(addr.clone(), LEVEL_2).await;
difficulty_factor = addr.send(Visitor).await.unwrap();
assert_eq!(difficulty_factor, LEVEL_2.1);
difficulty_factor = addr.send(Visitor).await.unwrap().unwrap();
assert_eq!(difficulty_factor.difficulty_factor, LEVEL_2.1);
}
#[actix_rt::test]
async fn counter_defense_loosenup_works() {
use actix::clock::delay_for;
let addr = get_counter().start();
let addr: MyActor = get_counter().start();
race(addr.clone(), LEVEL_2).await;
race(addr.clone(), LEVEL_2).await;
let mut difficulty_factor = addr.send(Visitor).await.unwrap();
assert_eq!(difficulty_factor, LEVEL_2.1);
let mut difficulty_factor = addr.send(Visitor).await.unwrap().unwrap();
assert_eq!(difficulty_factor.difficulty_factor, LEVEL_2.1);
let duration = Duration::new(DURATION, 0);
delay_for(duration).await;
difficulty_factor = addr.send(Visitor).await.unwrap();
assert_eq!(difficulty_factor, LEVEL_1.1);
difficulty_factor = addr.send(Visitor).await.unwrap().unwrap();
assert_eq!(difficulty_factor.difficulty_factor, LEVEL_1.1);
}
}

View file

@ -49,6 +49,10 @@ pub enum CaptchaError {
/// Difficulty factor should increase with level
#[display(fmt = "Difficulty factor should increase with level")]
DecreaseingDifficultyFactor,
/// Difficulty factor should increase with level
#[display(fmt = "Actor mailbox error")]
MailboxError,
}
/// [Result] datatype for m_captcha

92
src/hashcache.rs Normal file
View file

@ -0,0 +1,92 @@
use std::collections::HashMap;
use std::sync::Arc;
use actix::prelude::*;
use async_trait::async_trait;
use crate::config::PoWConfig;
use crate::config::Save;
use crate::errors::*;
#[derive(Clone, Default)]
pub struct HashCache(HashMap<String, u32>);
impl HashCache {
fn save(&mut self, config: Arc<PoWConfig>) -> CaptchaResult<()> {
self.0
.insert(config.string.clone(), config.difficulty_factor);
Ok(())
}
fn retrive(&mut self, string: Arc<String>) -> CaptchaResult<Option<u32>> {
if let Some(difficulty_factor) = self.0.get(&*string) {
Ok(Some(difficulty_factor.to_owned()))
} else {
Ok(None)
}
}
}
impl Save for HashCache {}
impl Actor for HashCache {
type Context = Context<Self>;
}
/// Message to decrement the visitor count
#[derive(Message)]
#[rtype(result = "CaptchaResult<()>")]
pub struct Cache(pub Arc<PoWConfig>);
impl Handler<Cache> for HashCache {
type Result = MessageResult<Cache>;
fn handle(&mut self, msg: Cache, _ctx: &mut Self::Context) -> Self::Result {
// if let Err(e) = self.save(msg.0.clone()) {
// MessageResult(Err(e))
// } else {
// MessageResult(Ok(msg.0))
// }
MessageResult(self.save(msg.0))
}
}
/// Message to decrement the visitor count
#[derive(Message)]
#[rtype(result = "CaptchaResult<Option<u32>>")]
pub struct Retrive(pub Arc<String>);
impl Handler<Retrive> for HashCache {
type Result = MessageResult<Retrive>;
fn handle(&mut self, msg: Retrive, _ctx: &mut Self::Context) -> Self::Result {
MessageResult(self.retrive(msg.0.clone()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[actix_rt::test]
async fn counter_defense_tightenup_works() {
let addr = HashCache::default().start();
let p = Arc::new("ewerw".to_string());
addr.send(Retrive(p)).await.unwrap();
}
//
// #[actix_rt::test]
// async fn counter_defense_loosenup_works() {
// use actix::clock::delay_for;
// let addr: MyActor = get_counter().start();
//
// race(addr.clone(), LEVEL_2).await;
// race(addr.clone(), LEVEL_2).await;
// let mut difficulty_factor = addr.send(Visitor).await.unwrap();
// assert_eq!(difficulty_factor.difficulty_factor, LEVEL_2.1);
//
// let duration = Duration::new(DURATION, 0);
// delay_for(duration).await;
//
// difficulty_factor = addr.send(Visitor).await.unwrap();
// assert_eq!(difficulty_factor.difficulty_factor, LEVEL_1.1);
// }
}

View file

@ -38,7 +38,7 @@
//! ## Example:
//!
//! ```rust
//! use m_captcha::{LevelBuilder, DefenseBuilder, message::Visitor, MCaptchaBuilder};
//! use m_captcha::{LevelBuilder, cache::HashCache, DefenseBuilder, message::Visitor, MCaptchaBuilder};
//! // traits from actix needs to be in scope for starting actor
//! use actix::prelude::*;
//!
@ -74,11 +74,14 @@
//! .build()
//! .unwrap();
//!
//! let cache = HashCache::default().start();
//!
//! // create and start MCaptcha actor
//! let mcaptcha = MCaptchaBuilder::default()
//! .defense(defense)
//! // leaky bucket algorithm's emission interval
//! .duration(30)
//! .cache(cache)
//! .build()
//! .unwrap()
//! .start();
@ -90,14 +93,20 @@
//! }
//! ```
pub mod counter;
pub mod config;
pub mod defense;
pub mod errors;
pub mod hashcache;
/// message datatypes to interact with [MCaptcha] actor
pub mod message {
pub use crate::counter::Visitor;
pub use crate::config::Visitor;
}
pub use counter::{MCaptcha, MCaptchaBuilder};
/// message datatypes to interact with [MCaptcha] actor
pub mod cache {
pub use crate::hashcache::HashCache;
}
pub use config::{MCaptcha, MCaptchaBuilder};
pub use defense::{Defense, DefenseBuilder, LevelBuilder};