mirror of https://github.com/breard-r/acmed.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
123 lines
2.7 KiB
123 lines
2.7 KiB
use crate::acme_proto::structs::Directory;
|
|
use crate::duration::parse_duration;
|
|
use acme_common::error::Error;
|
|
use std::cmp;
|
|
use std::thread;
|
|
use std::time::{Duration, Instant};
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Endpoint {
|
|
pub name: String,
|
|
pub url: String,
|
|
pub tos_agreed: bool,
|
|
pub nonce: Option<String>,
|
|
pub rl: RateLimit,
|
|
pub dir: Directory,
|
|
pub root_certificates: Vec<String>,
|
|
}
|
|
|
|
impl Endpoint {
|
|
pub fn new(
|
|
name: &str,
|
|
url: &str,
|
|
tos_agreed: bool,
|
|
limits: &[(usize, String)],
|
|
root_certs: &[String],
|
|
) -> Result<Self, Error> {
|
|
Ok(Self {
|
|
name: name.to_string(),
|
|
url: url.to_string(),
|
|
tos_agreed,
|
|
nonce: None,
|
|
rl: RateLimit::new(limits)?,
|
|
dir: Directory {
|
|
meta: None,
|
|
new_nonce: String::new(),
|
|
new_account: String::new(),
|
|
new_order: String::new(),
|
|
new_authz: None,
|
|
revoke_cert: String::new(),
|
|
key_change: String::new(),
|
|
},
|
|
root_certificates: root_certs.to_vec(),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct RateLimit {
|
|
limits: Vec<(usize, Duration)>,
|
|
query_log: Vec<Instant>,
|
|
}
|
|
|
|
impl RateLimit {
|
|
pub fn new(raw_limits: &[(usize, String)]) -> Result<Self, Error> {
|
|
let mut limits = vec![];
|
|
for (nb, raw_duration) in raw_limits.iter() {
|
|
let parsed_duration = parse_duration(raw_duration)?;
|
|
limits.push((*nb, parsed_duration));
|
|
}
|
|
limits.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
|
|
limits.reverse();
|
|
Ok(Self {
|
|
limits,
|
|
query_log: vec![],
|
|
})
|
|
}
|
|
|
|
pub fn block_until_allowed(&mut self) {
|
|
if self.limits.is_empty() {
|
|
return;
|
|
}
|
|
let sleep_duration = self.get_sleep_duration();
|
|
loop {
|
|
self.prune_log();
|
|
if self.request_allowed() {
|
|
self.query_log.push(Instant::now());
|
|
return;
|
|
}
|
|
// TODO: find a better sleep duration
|
|
thread::sleep(sleep_duration);
|
|
}
|
|
}
|
|
|
|
fn get_sleep_duration(&self) -> Duration {
|
|
let (nb_req, min_duration) = match self.limits.last() {
|
|
Some((n, d)) => (*n as u64, *d),
|
|
None => {
|
|
return Duration::from_millis(0);
|
|
}
|
|
};
|
|
let nb_mili = match min_duration.as_secs() {
|
|
0 | 1 => crate::MIN_RATE_LIMIT_SLEEP_MILISEC,
|
|
n => {
|
|
let a = n * 200 / nb_req;
|
|
let a = cmp::min(a, crate::MAX_RATE_LIMIT_SLEEP_MILISEC);
|
|
cmp::max(a, crate::MIN_RATE_LIMIT_SLEEP_MILISEC)
|
|
}
|
|
};
|
|
Duration::from_millis(nb_mili)
|
|
}
|
|
|
|
fn request_allowed(&self) -> bool {
|
|
for (max_allowed, duration) in self.limits.iter() {
|
|
let max_date = Instant::now() - *duration;
|
|
let nb_req = self
|
|
.query_log
|
|
.iter()
|
|
.filter(move |x| **x > max_date)
|
|
.count();
|
|
if nb_req >= *max_allowed {
|
|
return false;
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
fn prune_log(&mut self) {
|
|
if let Some((_, max_limit)) = self.limits.first() {
|
|
let prune_date = Instant::now() - *max_limit;
|
|
self.query_log.retain(move |&d| d > prune_date);
|
|
}
|
|
}
|
|
}
|