use std::time::Duration;
use actix::clock::sleep;
use actix::dev::*;
use serde::{Deserialize, Serialize};
use crate::master::AddVisitorResult;
use crate::mcaptcha::MCaptcha;
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Counter(MCaptcha);
impl From<MCaptcha> for Counter {
fn from(m: MCaptcha) -> Counter {
Counter(m)
}
}
impl Actor for Counter {
type Context = Context<Self>;
}
#[derive(Message)]
#[rtype(result = "()")]
struct DeleteVisitor;
impl Handler<DeleteVisitor> for Counter {
type Result = ();
fn handle(&mut self, _msg: DeleteVisitor, _ctx: &mut Self::Context) -> Self::Result {
self.0.decrement_visitor_by(1);
}
}
#[derive(Message)]
#[rtype(result = "AddVisitorResult")]
pub struct AddVisitor;
impl Handler<AddVisitor> for Counter {
type Result = MessageResult<AddVisitor>;
fn handle(&mut self, _: AddVisitor, ctx: &mut Self::Context) -> Self::Result {
let addr = ctx.address();
let duration: Duration = Duration::new(self.0.get_duration(), 0);
let wait_for = async move {
sleep(duration).await;
addr.send(DeleteVisitor).await.unwrap();
}
.into_actor(self);
ctx.spawn(wait_for);
self.0.add_visitor();
MessageResult(AddVisitorResult::new(&self.0))
}
}
#[derive(Message)]
#[rtype(result = "u32")]
pub struct GetCurrentVisitorCount;
impl Handler<GetCurrentVisitorCount> for Counter {
type Result = MessageResult<GetCurrentVisitorCount>;
fn handle(&mut self, _: GetCurrentVisitorCount, _ctx: &mut Self::Context) -> Self::Result {
MessageResult(self.0.get_visitors())
}
}
#[derive(Message)]
#[rtype(result = "()")]
pub struct Stop;
impl Handler<Stop> for Counter {
type Result = ();
fn handle(&mut self, _: Stop, ctx: &mut Self::Context) -> Self::Result {
ctx.stop()
}
}
#[derive(Message)]
#[rtype(result = "MCaptcha")]
pub struct GetInternalData;
impl Handler<GetInternalData> for Counter {
type Result = MessageResult<GetInternalData>;
fn handle(&mut self, _: GetInternalData, _ctx: &mut Self::Context) -> Self::Result {
MessageResult(self.0.clone())
}
}
#[derive(Message)]
#[rtype(result = "()")]
struct BulkDecrement(u32);
impl Handler<BulkDecrement> for Counter {
type Result = ();
fn handle(&mut self, msg: BulkDecrement, _ctx: &mut Self::Context) -> Self::Result {
self.0.decrement_visitor_by(msg.0);
}
}
#[derive(Message)]
#[rtype(result = "()")]
pub struct SetInternalData(pub MCaptcha);
impl Handler<SetInternalData> for Counter {
type Result = MessageResult<SetInternalData>;
fn handle(&mut self, d: SetInternalData, ctx: &mut Self::Context) -> Self::Result {
let addr = ctx.address();
self.0 = d.0;
let duration: Duration = Duration::new(self.0.get_duration(), 0);
let count = self.0.get_visitors();
let wait_for = async move {
sleep(duration).await;
addr.send(BulkDecrement(count)).await.unwrap();
}
.into_actor(self);
ctx.spawn(wait_for);
MessageResult(())
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::defense::*;
use crate::errors::*;
use crate::mcaptcha;
use crate::mcaptcha::MCaptchaBuilder;
pub const LEVEL_1: (u32, u32) = (50, 50);
pub const LEVEL_2: (u32, u32) = (500, 500);
pub const DURATION: u64 = 5;
type MyActor = Addr<Counter>;
pub fn get_defense() -> Defense {
DefenseBuilder::default()
.add_level(
LevelBuilder::default()
.visitor_threshold(LEVEL_1.0)
.difficulty_factor(LEVEL_1.1)
.unwrap()
.build()
.unwrap(),
)
.unwrap()
.add_level(
LevelBuilder::default()
.visitor_threshold(LEVEL_2.0)
.difficulty_factor(LEVEL_2.1)
.unwrap()
.build()
.unwrap(),
)
.unwrap()
.build()
.unwrap()
}
async fn race(addr: Addr<Counter>, count: (u32, u32)) {
for _ in 0..count.0 as usize - 1 {
let _ = addr.send(AddVisitor).await.unwrap();
}
}
pub fn get_counter() -> Counter {
get_mcaptcha().into()
}
pub fn get_mcaptcha() -> MCaptcha {
MCaptchaBuilder::default()
.defense(get_defense())
.duration(DURATION)
.build()
.unwrap()
}
#[test]
fn mcaptcha_decrement_by_works() {
let mut m = get_mcaptcha();
for _ in 0..100 {
m.add_visitor();
}
m.decrement_visitor_by(50);
assert_eq!(m.get_visitors(), 50);
m.decrement_visitor_by(500);
assert_eq!(m.get_visitors(), 0);
}
#[actix_rt::test]
async fn counter_defense_tightenup_works() {
let addr: MyActor = get_counter().start();
let mut mcaptcha = addr.send(AddVisitor).await.unwrap();
assert_eq!(mcaptcha.difficulty_factor, LEVEL_1.0);
race(addr.clone(), LEVEL_2).await;
mcaptcha = addr.send(AddVisitor).await.unwrap();
assert_eq!(mcaptcha.difficulty_factor, LEVEL_2.1);
}
#[actix_rt::test]
async fn counter_defense_loosenup_works() {
let addr: MyActor = get_counter().start();
race(addr.clone(), LEVEL_2).await;
race(addr.clone(), LEVEL_2).await;
let mut mcaptcha = addr.send(AddVisitor).await.unwrap();
assert_eq!(mcaptcha.difficulty_factor, LEVEL_2.1);
let duration = Duration::new(DURATION, 0);
sleep(duration).await;
mcaptcha = addr.send(AddVisitor).await.unwrap();
assert_eq!(mcaptcha.difficulty_factor, LEVEL_1.1);
}
#[test]
fn test_mcatcptha_builder() {
let defense = get_defense();
let m = MCaptchaBuilder::default()
.duration(0)
.defense(defense.clone())
.build();
assert_eq!(m.err(), Some(CaptchaError::CaptchaDurationZero));
let m = MCaptchaBuilder::default().duration(30).build();
assert_eq!(
m.err(),
Some(CaptchaError::PleaseSetValue("defense".into()))
);
let m = MCaptchaBuilder::default().defense(defense).build();
assert_eq!(
m.err(),
Some(CaptchaError::PleaseSetValue("duration".into()))
);
}
#[actix_rt::test]
async fn get_current_visitor_count_works() {
let addr: MyActor = get_counter().start();
addr.send(AddVisitor).await.unwrap();
addr.send(AddVisitor).await.unwrap();
addr.send(AddVisitor).await.unwrap();
addr.send(AddVisitor).await.unwrap();
let count = addr.send(GetCurrentVisitorCount).await.unwrap();
assert_eq!(count, 4);
}
#[actix_rt::test]
#[should_panic]
async fn stop_works() {
let addr: MyActor = get_counter().start();
addr.send(Stop).await.unwrap();
addr.send(AddVisitor).await.unwrap();
}
#[actix_rt::test]
async fn get_set_internal_data_works() {
let addr: MyActor = get_counter().start();
let mut mcaptcha = addr.send(GetInternalData).await.unwrap();
mcaptcha.add_visitor();
addr.send(SetInternalData(mcaptcha.clone())).await.unwrap();
assert_eq!(
addr.send(GetInternalData).await.unwrap().get_visitors(),
mcaptcha.get_visitors()
);
let duration = Duration::new(mcaptcha.get_duration() + 3, 0);
sleep(duration).await;
assert_eq!(addr.send(GetCurrentVisitorCount).await.unwrap(), 0);
}
#[actix_rt::test]
async fn bulk_delete_works() {
let addr: MyActor = get_counter().start();
addr.send(AddVisitor).await.unwrap();
addr.send(AddVisitor).await.unwrap();
assert_eq!(addr.send(GetCurrentVisitorCount).await.unwrap(), 2);
addr.send(BulkDecrement(3)).await.unwrap();
assert_eq!(addr.send(GetCurrentVisitorCount).await.unwrap(), 0);
}
}