use std::fmt;
use std::num::NonZeroU64;
use std::rc::Rc;
use std::time::{SystemTime, UNIX_EPOCH};
use crate::constants;
use crate::core::Timestamp;
use crate::error::ConfigError;
pub trait WallClock {
fn now(&self) -> Timestamp;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct SystemClock;
impl WallClock for SystemClock {
fn now(&self) -> Timestamp {
match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(d) => Timestamp::new(d.as_secs(), d.subsec_nanos()),
Err(_) => Timestamp::new(0, 0),
}
}
}
#[derive(Clone)]
pub struct Config {
intro_queue_cap: usize,
intro_max_per_source: usize,
epoch_size: NonZeroU64,
stream_window: u64,
connection_window: u64,
clock: Rc<dyn WallClock>,
}
impl fmt::Debug for Config {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Config")
.field("intro_queue_cap", &self.intro_queue_cap)
.field("intro_max_per_source", &self.intro_max_per_source)
.field("epoch_size", &self.epoch_size)
.field("stream_window", &self.stream_window)
.field("connection_window", &self.connection_window)
.finish_non_exhaustive()
}
}
impl Default for Config {
fn default() -> Self {
Self {
intro_queue_cap: constants::INTRO_QUEUE_CAP,
intro_max_per_source: constants::INTRO_MAX_PER_SOURCE,
epoch_size: Config::DEFAULT_EPOCH_SIZE,
stream_window: Config::DEFAULT_STREAM_WINDOW,
connection_window: Config::DEFAULT_CONNECTION_WINDOW,
clock: Rc::new(SystemClock),
}
}
}
impl Config {
pub const DEFAULT_EPOCH_SIZE: NonZeroU64 = match NonZeroU64::new(constants::REKEY_EPOCH_MSGS) {
Some(n) => n,
None => panic!("REKEY_EPOCH_MSGS is nonzero"),
};
pub const DEFAULT_STREAM_WINDOW: u64 = constants::INITIAL_MAX_STREAM_DATA;
pub const DEFAULT_CONNECTION_WINDOW: u64 = constants::INITIAL_MAX_DATA;
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_intro_queue_cap(mut self, cap: usize) -> Self {
self.intro_queue_cap = cap;
self
}
#[must_use]
pub fn with_intro_max_per_source(mut self, cap: usize) -> Self {
self.intro_max_per_source = cap;
self
}
#[must_use]
pub fn with_epoch_size(mut self, epoch_size: NonZeroU64) -> Self {
self.epoch_size = epoch_size;
self
}
pub fn with_flow_windows(mut self, stream: u64, connection: u64) -> Result<Self, ConfigError> {
if stream < Self::DEFAULT_STREAM_WINDOW || connection < Self::DEFAULT_CONNECTION_WINDOW {
return Err(ConfigError::WindowTooSmall);
}
if stream > crate::varint::VarInt::MAX_VALUE
|| connection > crate::varint::VarInt::MAX_VALUE
{
return Err(ConfigError::WindowTooLarge);
}
if stream > connection {
return Err(ConfigError::StreamWindowAboveConnection);
}
self.stream_window = stream;
self.connection_window = connection;
Ok(self)
}
#[must_use]
pub fn with_clock(mut self, clock: Rc<dyn WallClock>) -> Self {
self.clock = clock;
self
}
pub fn intro_queue_cap(&self) -> usize {
self.intro_queue_cap
}
pub fn intro_max_per_source(&self) -> usize {
self.intro_max_per_source
}
pub fn epoch_size(&self) -> NonZeroU64 {
self.epoch_size
}
pub fn stream_window(&self) -> u64 {
self.stream_window
}
pub fn connection_window(&self) -> u64 {
self.connection_window
}
pub fn clock(&self) -> &dyn WallClock {
&*self.clock
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_default_epoch_size_is_rekey_epoch_msgs() {
assert_eq!(
Config::new().epoch_size().get(),
constants::REKEY_EPOCH_MSGS
);
assert_eq!(
Config::DEFAULT_EPOCH_SIZE.get(),
constants::REKEY_EPOCH_MSGS
);
}
#[test]
fn the_epoch_size_override_takes_effect() {
let config = Config::new().with_epoch_size(NonZeroU64::new(8).unwrap());
assert_eq!(config.epoch_size().get(), 8);
}
#[test]
fn the_default_windows_are_the_ratified_constants() {
let config = Config::new();
assert_eq!(config.stream_window(), constants::INITIAL_MAX_STREAM_DATA);
assert_eq!(config.connection_window(), constants::INITIAL_MAX_DATA);
assert_eq!(
Config::DEFAULT_STREAM_WINDOW,
constants::INITIAL_MAX_STREAM_DATA
);
assert_eq!(
Config::DEFAULT_CONNECTION_WINDOW,
constants::INITIAL_MAX_DATA
);
}
#[test]
fn the_window_override_takes_effect() {
let config = Config::new()
.with_flow_windows(1 << 20, 1 << 23)
.expect("a raise");
assert_eq!(config.stream_window(), 1 << 20);
assert_eq!(config.connection_window(), 1 << 23);
}
#[test]
fn a_window_below_its_ratified_default_is_refused() {
assert_eq!(
Config::new()
.with_flow_windows(
constants::INITIAL_MAX_STREAM_DATA - 1,
constants::INITIAL_MAX_DATA,
)
.unwrap_err(),
ConfigError::WindowTooSmall,
);
assert_eq!(
Config::new()
.with_flow_windows(
constants::INITIAL_MAX_STREAM_DATA,
constants::INITIAL_MAX_DATA - 1,
)
.unwrap_err(),
ConfigError::WindowTooSmall,
);
let at_the_defaults = Config::new()
.with_flow_windows(
constants::INITIAL_MAX_STREAM_DATA,
constants::INITIAL_MAX_DATA,
)
.expect("the defaults restated are a legal no-op raise");
assert_eq!(
at_the_defaults.stream_window(),
constants::INITIAL_MAX_STREAM_DATA
);
}
#[test]
fn a_window_past_the_varint_bound_is_refused() {
let max = crate::varint::VarInt::MAX_VALUE;
assert_eq!(
Config::new()
.with_flow_windows(max, max)
.unwrap()
.stream_window(),
max,
"the largest encodable absolute offset is admissible",
);
assert_eq!(
Config::new()
.with_flow_windows(max + 1, max + 1)
.unwrap_err(),
ConfigError::WindowTooLarge,
);
assert_eq!(
Config::new().with_flow_windows(max, max + 1).unwrap_err(),
ConfigError::WindowTooLarge,
"the connection window is checked on its own, not only via the pair",
);
}
#[test]
fn the_stream_window_may_not_exceed_the_connection_window() {
assert_eq!(
Config::new()
.with_flow_windows(1 << 23, 1 << 20)
.unwrap_err(),
ConfigError::StreamWindowAboveConnection,
);
}
}