use std::time::Duration;
use rand::Rng;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RtuTimingFaultConfig {
#[serde(default)]
pub violate_interframe_delay: Option<Duration>,
#[serde(default)]
pub inject_interchar_gap: Option<InterCharGapConfig>,
#[serde(default)]
pub bus_collision: Option<BusCollisionConfig>,
#[serde(default)]
pub byte_jitter: Option<ByteJitterConfig>,
}
impl Default for RtuTimingFaultConfig {
fn default() -> Self {
Self {
violate_interframe_delay: None,
inject_interchar_gap: None,
bus_collision: None,
byte_jitter: None,
}
}
}
impl RtuTimingFaultConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_interframe_violation(mut self, delay: Duration) -> Self {
self.violate_interframe_delay = Some(delay);
self
}
pub fn with_interchar_gap(mut self, config: InterCharGapConfig) -> Self {
self.inject_interchar_gap = Some(config);
self
}
pub fn with_bus_collision(mut self, config: BusCollisionConfig) -> Self {
self.bus_collision = Some(config);
self
}
pub fn with_byte_jitter(mut self, config: ByteJitterConfig) -> Self {
self.byte_jitter = Some(config);
self
}
pub fn is_active(&self) -> bool {
self.violate_interframe_delay.is_some()
|| self.inject_interchar_gap.is_some()
|| self.bus_collision.is_some()
|| self.byte_jitter.is_some()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InterCharGapConfig {
pub position: GapPosition,
pub gap_duration: Duration,
}
impl InterCharGapConfig {
pub fn at_offset(offset: usize, duration: Duration) -> Self {
Self {
position: GapPosition::FixedOffset(offset),
gap_duration: duration,
}
}
pub fn random(duration: Duration) -> Self {
Self {
position: GapPosition::Random,
gap_duration: duration,
}
}
pub fn after_fc(duration: Duration) -> Self {
Self {
position: GapPosition::AfterFunctionCode,
gap_duration: duration,
}
}
pub fn compute_offset(&self, frame_length: usize) -> usize {
match self.position {
GapPosition::FixedOffset(offset) => offset.min(frame_length.saturating_sub(1)),
GapPosition::Random => {
if frame_length <= 2 {
1
} else {
let mut rng = rand::thread_rng();
rng.gen_range(1..frame_length - 1)
}
}
GapPosition::AfterFunctionCode => {
2.min(frame_length.saturating_sub(1))
}
GapPosition::BeforeCrc => {
frame_length.saturating_sub(2)
}
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GapPosition {
FixedOffset(usize),
Random,
AfterFunctionCode,
BeforeCrc,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BusCollisionConfig {
pub mode: CollisionMode,
#[serde(default = "default_garbage_bytes")]
pub garbage_byte_count: usize,
#[serde(default = "default_collision_probability")]
pub probability: f64,
}
fn default_garbage_bytes() -> usize {
8
}
fn default_collision_probability() -> f64 {
0.1
}
impl BusCollisionConfig {
pub fn garbage(byte_count: usize, probability: f64) -> Self {
Self {
mode: CollisionMode::GarbageInject,
garbage_byte_count: byte_count,
probability: probability.clamp(0.0, 1.0),
}
}
pub fn overlap(probability: f64) -> Self {
Self {
mode: CollisionMode::OverlapResponse,
garbage_byte_count: 0,
probability: probability.clamp(0.0, 1.0),
}
}
pub fn should_collide(&self) -> bool {
if (self.probability - 1.0).abs() < f64::EPSILON {
return true;
}
if self.probability <= 0.0 {
return false;
}
let mut rng = rand::thread_rng();
rng.gen::<f64>() < self.probability
}
pub fn generate_garbage(&self) -> Vec<u8> {
let mut rng = rand::thread_rng();
(0..self.garbage_byte_count)
.map(|_| rng.gen::<u8>())
.collect()
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CollisionMode {
GarbageInject,
OverlapResponse,
EchoFrame,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ByteJitterConfig {
pub min_delay: Duration,
pub max_delay: Duration,
#[serde(default = "default_jitter_interval")]
pub interval: usize,
}
fn default_jitter_interval() -> usize {
1
}
impl ByteJitterConfig {
pub fn new(min_delay: Duration, max_delay: Duration) -> Self {
Self {
min_delay,
max_delay,
interval: 1,
}
}
pub fn with_interval(mut self, n: usize) -> Self {
self.interval = n.max(1);
self
}
pub fn compute_delay(&self) -> Duration {
if self.min_delay >= self.max_delay {
return self.min_delay;
}
let mut rng = rand::thread_rng();
let min_us = self.min_delay.as_micros() as u64;
let max_us = self.max_delay.as_micros() as u64;
Duration::from_micros(rng.gen_range(min_us..=max_us))
}
}
#[derive(Debug)]
pub struct TimingPlan {
pub segments: Vec<TimingSegment>,
}
#[derive(Debug)]
pub struct TimingSegment {
pub data: Vec<u8>,
pub delay_before: Duration,
}
impl RtuTimingFaultConfig {
pub fn build_timing_plan(&self, frame_bytes: &[u8]) -> TimingPlan {
let mut segments = Vec::new();
let initial_delay = self.violate_interframe_delay.unwrap_or(Duration::ZERO);
if let Some(ref collision) = self.bus_collision {
if collision.should_collide() {
let garbage = collision.generate_garbage();
if !garbage.is_empty() {
segments.push(TimingSegment {
data: garbage,
delay_before: initial_delay,
});
let gap = Duration::from_micros(500);
self.add_frame_segments(&mut segments, frame_bytes, gap);
return TimingPlan { segments };
}
}
}
if let Some(ref gap_config) = self.inject_interchar_gap {
let offset = gap_config.compute_offset(frame_bytes.len());
if offset > 0 && offset < frame_bytes.len() {
segments.push(TimingSegment {
data: frame_bytes[..offset].to_vec(),
delay_before: initial_delay,
});
segments.push(TimingSegment {
data: frame_bytes[offset..].to_vec(),
delay_before: gap_config.gap_duration,
});
return TimingPlan { segments };
}
}
if let Some(ref jitter) = self.byte_jitter {
let mut current_delay = initial_delay;
for (i, &byte) in frame_bytes.iter().enumerate() {
segments.push(TimingSegment {
data: vec![byte],
delay_before: current_delay,
});
current_delay = if jitter.interval > 0 && (i + 1) % jitter.interval == 0 {
jitter.compute_delay()
} else {
Duration::ZERO
};
}
return TimingPlan { segments };
}
segments.push(TimingSegment {
data: frame_bytes.to_vec(),
delay_before: initial_delay,
});
TimingPlan { segments }
}
fn add_frame_segments(
&self,
segments: &mut Vec<TimingSegment>,
frame_bytes: &[u8],
initial_delay: Duration,
) {
if let Some(ref gap_config) = self.inject_interchar_gap {
let offset = gap_config.compute_offset(frame_bytes.len());
if offset > 0 && offset < frame_bytes.len() {
segments.push(TimingSegment {
data: frame_bytes[..offset].to_vec(),
delay_before: initial_delay,
});
segments.push(TimingSegment {
data: frame_bytes[offset..].to_vec(),
delay_before: gap_config.gap_duration,
});
return;
}
}
segments.push(TimingSegment {
data: frame_bytes.to_vec(),
delay_before: initial_delay,
});
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = RtuTimingFaultConfig::default();
assert!(!config.is_active());
}
#[test]
fn test_interframe_violation() {
let config =
RtuTimingFaultConfig::new().with_interframe_violation(Duration::from_micros(500));
assert!(config.is_active());
let frame = vec![0x01, 0x03, 0x02, 0x00, 0x64, 0xB8, 0x44];
let plan = config.build_timing_plan(&frame);
assert_eq!(plan.segments.len(), 1);
assert_eq!(plan.segments[0].delay_before, Duration::from_micros(500));
assert_eq!(plan.segments[0].data, frame);
}
#[test]
fn test_interchar_gap_after_fc() {
let config = RtuTimingFaultConfig::new()
.with_interchar_gap(InterCharGapConfig::after_fc(Duration::from_millis(5)));
let frame = vec![0x01, 0x03, 0x02, 0x00, 0x64, 0xB8, 0x44];
let plan = config.build_timing_plan(&frame);
assert_eq!(plan.segments.len(), 2);
assert_eq!(plan.segments[0].data, vec![0x01, 0x03]); assert_eq!(plan.segments[1].data, vec![0x02, 0x00, 0x64, 0xB8, 0x44]); assert_eq!(plan.segments[1].delay_before, Duration::from_millis(5));
}
#[test]
fn test_interchar_gap_fixed_offset() {
let config = RtuTimingFaultConfig::new()
.with_interchar_gap(InterCharGapConfig::at_offset(3, Duration::from_millis(2)));
let frame = vec![0x01, 0x03, 0x02, 0x00, 0x64, 0xB8, 0x44];
let plan = config.build_timing_plan(&frame);
assert_eq!(plan.segments.len(), 2);
assert_eq!(plan.segments[0].data.len(), 3);
assert_eq!(plan.segments[1].data.len(), 4);
}
#[test]
fn test_byte_jitter() {
let config = RtuTimingFaultConfig::new().with_byte_jitter(ByteJitterConfig::new(
Duration::from_micros(100),
Duration::from_micros(500),
));
let frame = vec![0x01, 0x03, 0x02];
let plan = config.build_timing_plan(&frame);
assert_eq!(plan.segments.len(), 3); assert_eq!(plan.segments[0].data, vec![0x01]);
assert_eq!(plan.segments[1].data, vec![0x03]);
assert_eq!(plan.segments[2].data, vec![0x02]);
}
#[test]
fn test_bus_collision_garbage() {
let config =
RtuTimingFaultConfig::new().with_bus_collision(BusCollisionConfig::garbage(4, 1.0));
let frame = vec![0x01, 0x03, 0x02, 0x00, 0x64, 0xB8, 0x44];
let plan = config.build_timing_plan(&frame);
assert!(plan.segments.len() >= 2);
assert_eq!(plan.segments[0].data.len(), 4); }
#[test]
fn test_bus_collision_no_trigger() {
let config =
RtuTimingFaultConfig::new().with_bus_collision(BusCollisionConfig::garbage(4, 0.0));
let frame = vec![0x01, 0x03, 0x02];
let plan = config.build_timing_plan(&frame);
assert_eq!(plan.segments.len(), 1);
assert_eq!(plan.segments[0].data, frame);
}
#[test]
fn test_combined_interframe_and_gap() {
let config = RtuTimingFaultConfig::new()
.with_interframe_violation(Duration::from_micros(100))
.with_interchar_gap(InterCharGapConfig::at_offset(2, Duration::from_millis(3)));
let frame = vec![0x01, 0x03, 0x02, 0x00, 0x64];
let plan = config.build_timing_plan(&frame);
assert_eq!(plan.segments.len(), 2);
assert_eq!(plan.segments[0].delay_before, Duration::from_micros(100));
assert_eq!(plan.segments[1].delay_before, Duration::from_millis(3));
}
#[test]
fn test_gap_position_computation() {
let gap = InterCharGapConfig::at_offset(10, Duration::ZERO);
assert_eq!(gap.compute_offset(5), 4);
let gap = InterCharGapConfig::after_fc(Duration::ZERO);
assert_eq!(gap.compute_offset(10), 2);
assert_eq!(gap.compute_offset(1), 0);
let gap = InterCharGapConfig {
position: GapPosition::BeforeCrc,
gap_duration: Duration::ZERO,
};
assert_eq!(gap.compute_offset(7), 5); }
#[test]
fn test_byte_jitter_interval() {
let jitter = ByteJitterConfig::new(Duration::from_micros(100), Duration::from_micros(100))
.with_interval(2);
let config = RtuTimingFaultConfig::new().with_byte_jitter(jitter);
let frame = vec![0x01, 0x02, 0x03, 0x04];
let plan = config.build_timing_plan(&frame);
assert_eq!(plan.segments.len(), 4);
assert_eq!(plan.segments[2].delay_before, Duration::from_micros(100));
}
#[test]
fn test_byte_jitter_delay_range() {
let jitter = ByteJitterConfig::new(Duration::from_micros(100), Duration::from_micros(500));
for _ in 0..100 {
let delay = jitter.compute_delay();
assert!(delay >= Duration::from_micros(100));
assert!(delay <= Duration::from_micros(500));
}
}
}