//! Failure counting/capping use super::*; use std::{ fmt, error, }; /// A measure of failures, used to track or to check failures. #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord, Copy)] pub struct Failures { /// Number of failures happened back-to-back. pub seq: usize, /// Total number of failures over the socket's lifetime. /// /// Set allowed to `0` for unlimited. pub total: usize, /// Window of time to keep failures. pub window: Duration, /// Number of failures happened in the last `window` of time. pub last_window: usize, } /// When a failure cap is exceeded, which one is exceeded; and what is the limit that is exceeded? #[derive(Debug, Clone, Serialize, Deserialize)] pub enum FailureCapExceeded { /// Too many sequential errors Sequential(usize), /// Too many total errors Total(usize), /// Too many errors in the refresh window Windowed(usize, Duration), } impl error::Error for FailureCapExceeded{} impl fmt::Display for FailureCapExceeded { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Sequential(_) => write!(f, "too many sequential errors"), Self::Total(_) => write!(f, "too many total errors"), Self::Windowed(_, _) => write!(f, "too many errors in refresh window"), } } } impl FailureCapExceeded { /// Convert into a detailed report. (This shouldn't be shared with peer, probably.) pub fn into_detailed_report(self) -> eyre::Report { let rep = eyre::Report::from(&self); match self { Self::Sequential(cap) => rep.with_section(|| cap.header("Exceeded limit")), Self::Total(cap) => rep.with_section(|| cap.header("Exceeded limit")), Self::Windowed(cap, w) => rep.with_section(|| cap.header("Exceeded limit")) .with_section(|| format!("{:?}", w).header("Refresh window was")) } } } impl Failures { /// Default allowed failure parameters. pub const DEFAULT_ALLOWED: Self = Self { seq: 10, total: 65536, window: Duration::from_secs(10), last_window: 5, }; /// Has this `Failures` exceeded failure cap `other`? pub fn cap_check(&self, other: &Self) -> Result<(), FailureCapExceeded> { macro_rules! chk { ($name:ident, $err:expr) => { if other.$name != 0 && (self.$name >= other.$name) { return Err($err)?; } } } chk!(seq, FailureCapExceeded::Sequential(other.seq)); chk!(total, FailureCapExceeded::Total(other.total)); //debug_assert!(other.window == self.window); //TODO: Should we disallow this? chk!(last_window, FailureCapExceeded::Windowed(other.last_window, self.window)); Ok(()) } } impl Default for Failures { #[inline] fn default() -> Self { Self::DEFAULT_ALLOWED } }