use std::sync::atomic::{AtomicU64, Ordering};
use super::stats::{FaultStats, FaultStatsSnapshot};
use super::targeting::FaultTarget;
use super::{FaultAction, ModbusFault, ModbusFaultContext};
pub struct NoResponseFault {
target: FaultTarget,
stats: FaultStats,
consecutive_drops: AtomicU64,
}
impl NoResponseFault {
pub fn new(target: FaultTarget) -> Self {
Self {
target,
stats: FaultStats::new(),
consecutive_drops: AtomicU64::new(0),
}
}
pub fn consecutive_drops(&self) -> u64 {
self.consecutive_drops.load(Ordering::Acquire)
}
pub fn reset_consecutive(&self) {
self.consecutive_drops.store(0, Ordering::Release);
}
}
impl ModbusFault for NoResponseFault {
fn fault_type(&self) -> &'static str {
"no_response"
}
fn is_enabled(&self) -> bool {
self.stats.is_enabled()
}
fn set_enabled(&self, enabled: bool) {
self.stats.set_enabled(enabled);
}
fn should_activate(&self, ctx: &ModbusFaultContext) -> bool {
self.stats.record_check();
self.target.should_activate(ctx.unit_id, ctx.function_code)
}
fn apply(&self, _ctx: &ModbusFaultContext) -> FaultAction {
self.stats.record_activation();
self.stats.record_affected();
self.consecutive_drops.fetch_add(1, Ordering::Relaxed);
FaultAction::DropResponse
}
fn stats(&self) -> FaultStatsSnapshot {
self.stats.snapshot()
}
fn reset_stats(&self) {
self.stats.reset();
self.consecutive_drops.store(0, Ordering::Release);
}
fn is_short_circuit(&self) -> bool {
true
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_ctx() -> ModbusFaultContext {
ModbusFaultContext::tcp(
1,
0x03,
&[0x03, 0x00, 0x00, 0x00, 0x01],
&[0x03, 0x02, 0x00, 0x64],
1,
1,
)
}
#[test]
fn test_always_drops() {
let fault = NoResponseFault::new(FaultTarget::new());
let action = fault.apply(&test_ctx());
assert!(matches!(action, FaultAction::DropResponse));
}
#[test]
fn test_consecutive_drops() {
let fault = NoResponseFault::new(FaultTarget::new());
let ctx = test_ctx();
assert_eq!(fault.consecutive_drops(), 0);
fault.apply(&ctx);
assert_eq!(fault.consecutive_drops(), 1);
fault.apply(&ctx);
assert_eq!(fault.consecutive_drops(), 2);
fault.apply(&ctx);
assert_eq!(fault.consecutive_drops(), 3);
}
#[test]
fn test_reset_consecutive() {
let fault = NoResponseFault::new(FaultTarget::new());
let ctx = test_ctx();
fault.apply(&ctx);
fault.apply(&ctx);
assert_eq!(fault.consecutive_drops(), 2);
fault.reset_consecutive();
assert_eq!(fault.consecutive_drops(), 0);
}
#[test]
fn test_is_short_circuit() {
let fault = NoResponseFault::new(FaultTarget::new());
assert!(fault.is_short_circuit());
}
#[test]
fn test_stats() {
let fault = NoResponseFault::new(FaultTarget::new());
let ctx = test_ctx();
assert!(fault.should_activate(&ctx));
fault.apply(&ctx);
fault.should_activate(&ctx);
fault.apply(&ctx);
let stats = fault.stats();
assert_eq!(stats.checks, 2);
assert_eq!(stats.activations, 2);
assert_eq!(stats.affected_requests, 2);
}
#[test]
fn test_reset_stats() {
let fault = NoResponseFault::new(FaultTarget::new());
let ctx = test_ctx();
fault.should_activate(&ctx);
fault.apply(&ctx);
fault.reset_stats();
let stats = fault.stats();
assert_eq!(stats.checks, 0);
assert_eq!(stats.activations, 0);
assert_eq!(fault.consecutive_drops(), 0);
}
#[test]
fn test_probability_targeting() {
let fault = NoResponseFault::new(FaultTarget::new().with_probability(0.0));
let ctx = test_ctx();
for _ in 0..100 {
assert!(!fault.should_activate(&ctx));
}
}
}