use datasize::DataSize;
use serde::{
de::{Deserializer, Error as SerdeError, Unexpected},
Deserialize, Serialize,
};
use tracing::error;
#[cfg(test)]
use super::Error;
const DEFAULT_INFECTION_TARGET: u8 = 3;
const DEFAULT_SATURATION_LIMIT_PERCENT: u8 = 80;
pub(super) const MAX_SATURATION_LIMIT_PERCENT: u8 = 99;
pub(super) const DEFAULT_FINISHED_ENTRY_DURATION_SECS: u64 = 60;
const DEFAULT_GOSSIP_REQUEST_TIMEOUT_SECS: u64 = 10;
const DEFAULT_GET_REMAINDER_TIMEOUT_SECS: u64 = 60;
#[cfg(test)]
const SMALL_TIMEOUTS_FINISHED_ENTRY_DURATION_SECS: u64 = 2;
#[cfg(test)]
const SMALL_TIMEOUTS_GOSSIP_REQUEST_TIMEOUT_SECS: u64 = 1;
#[cfg(test)]
const SMALL_TIMEOUTS_GET_REMAINDER_TIMEOUT_SECS: u64 = 1;
#[derive(Copy, Clone, DataSize, Debug, Deserialize, Serialize)]
pub struct Config {
infection_target: u8,
#[serde(deserialize_with = "deserialize_saturation_limit_percent")]
saturation_limit_percent: u8,
finished_entry_duration_secs: u64,
gossip_request_timeout_secs: u64,
get_remainder_timeout_secs: u64,
}
impl Config {
#[cfg(test)]
pub(crate) fn new(
infection_target: u8,
saturation_limit_percent: u8,
finished_entry_duration_secs: u64,
gossip_request_timeout_secs: u64,
get_remainder_timeout_secs: u64,
) -> Result<Self, Error> {
if saturation_limit_percent > MAX_SATURATION_LIMIT_PERCENT {
return Err(Error::InvalidSaturationLimit);
}
Ok(Config {
infection_target,
saturation_limit_percent,
finished_entry_duration_secs,
gossip_request_timeout_secs,
get_remainder_timeout_secs,
})
}
#[cfg(test)]
pub(crate) fn new_with_small_timeouts() -> Self {
Config {
finished_entry_duration_secs: SMALL_TIMEOUTS_FINISHED_ENTRY_DURATION_SECS,
gossip_request_timeout_secs: SMALL_TIMEOUTS_GOSSIP_REQUEST_TIMEOUT_SECS,
get_remainder_timeout_secs: SMALL_TIMEOUTS_GET_REMAINDER_TIMEOUT_SECS,
..Default::default()
}
}
pub(crate) fn infection_target(&self) -> u8 {
self.infection_target
}
pub(crate) fn saturation_limit_percent(&self) -> u8 {
self.saturation_limit_percent
}
pub(crate) fn finished_entry_duration_secs(&self) -> u64 {
self.finished_entry_duration_secs
}
pub(crate) fn gossip_request_timeout_secs(&self) -> u64 {
self.gossip_request_timeout_secs
}
pub(crate) fn get_remainder_timeout_secs(&self) -> u64 {
self.get_remainder_timeout_secs
}
}
impl Default for Config {
fn default() -> Self {
Config {
infection_target: DEFAULT_INFECTION_TARGET,
saturation_limit_percent: DEFAULT_SATURATION_LIMIT_PERCENT,
finished_entry_duration_secs: DEFAULT_FINISHED_ENTRY_DURATION_SECS,
gossip_request_timeout_secs: DEFAULT_GOSSIP_REQUEST_TIMEOUT_SECS,
get_remainder_timeout_secs: DEFAULT_GET_REMAINDER_TIMEOUT_SECS,
}
}
}
fn deserialize_saturation_limit_percent<'de, D>(deserializer: D) -> Result<u8, D::Error>
where
D: Deserializer<'de>,
{
let saturation_limit_percent = u8::deserialize(deserializer)?;
if saturation_limit_percent > MAX_SATURATION_LIMIT_PERCENT {
error!(
"saturation_limit_percent of {} is above {}",
saturation_limit_percent, MAX_SATURATION_LIMIT_PERCENT
);
return Err(SerdeError::invalid_value(
Unexpected::Unsigned(saturation_limit_percent as u64),
&"a value between 0 and 99 inclusive",
));
}
Ok(saturation_limit_percent)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn invalid_config_should_fail() {
let invalid_config = Config {
infection_target: 3,
saturation_limit_percent: MAX_SATURATION_LIMIT_PERCENT + 1,
finished_entry_duration_secs: DEFAULT_FINISHED_ENTRY_DURATION_SECS,
gossip_request_timeout_secs: DEFAULT_GOSSIP_REQUEST_TIMEOUT_SECS,
get_remainder_timeout_secs: DEFAULT_GET_REMAINDER_TIMEOUT_SECS,
};
let config_as_json = serde_json::to_string(&invalid_config).unwrap();
assert!(serde_json::from_str::<Config>(&config_as_json).is_err());
assert!(Config::new(
3,
MAX_SATURATION_LIMIT_PERCENT + 1,
DEFAULT_FINISHED_ENTRY_DURATION_SECS,
DEFAULT_GOSSIP_REQUEST_TIMEOUT_SECS,
DEFAULT_GET_REMAINDER_TIMEOUT_SECS,
)
.is_err())
}
}