use std::time::Duration;
use serde::{Deserialize, Serialize};
use super::targeting::FaultTarget;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FaultInjectionConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default)]
pub faults: Vec<FaultConfig>,
}
fn default_true() -> bool {
true
}
impl Default for FaultInjectionConfig {
fn default() -> Self {
Self {
enabled: true,
faults: Vec::new(),
}
}
}
impl FaultInjectionConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_fault(mut self, fault: FaultConfig) -> Self {
self.faults.push(fault);
self
}
pub fn with_enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FaultConfig {
#[serde(rename = "type")]
pub fault_type: FaultType,
#[serde(default)]
pub target: FaultTarget,
#[serde(default)]
pub config: FaultTypeConfig,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FaultType {
CrcCorruption,
WrongUnitId,
WrongFunctionCode,
WrongTransactionId,
TruncatedResponse,
ExtraData,
DelayedResponse,
NoResponse,
ExceptionInjection,
PartialFrame,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct FaultTypeConfig {
pub crc_mode: Option<CrcCorruptionMode>,
pub unit_id_mode: Option<UnitIdCorruptionMode>,
pub fixed_unit_id: Option<u8>,
pub fc_mode: Option<FcCorruptionMode>,
pub fixed_fc: Option<u8>,
pub tid_mode: Option<TidCorruptionMode>,
pub fixed_tid: Option<u16>,
pub truncation_mode: Option<TruncationMode>,
pub truncation_bytes: Option<usize>,
pub truncation_percentage: Option<f64>,
pub extra_data_mode: Option<ExtraDataMode>,
pub extra_bytes: Option<Vec<u8>>,
pub extra_count: Option<usize>,
pub delay_ms: Option<u64>,
pub jitter_ms: Option<u64>,
pub exception_code: Option<u8>,
pub partial_mode: Option<PartialFrameMode>,
pub partial_bytes: Option<usize>,
pub partial_percentage: Option<f64>,
}
impl Default for FaultTypeConfig {
fn default() -> Self {
Self {
crc_mode: None,
unit_id_mode: None,
fixed_unit_id: None,
fc_mode: None,
fixed_fc: None,
tid_mode: None,
fixed_tid: None,
truncation_mode: None,
truncation_bytes: None,
truncation_percentage: None,
extra_data_mode: None,
extra_bytes: None,
extra_count: None,
delay_ms: None,
jitter_ms: None,
exception_code: None,
partial_mode: None,
partial_bytes: None,
partial_percentage: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CrcCorruptionMode {
Zero,
Invert,
RandomXor,
SetValue,
SwapBytes,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum UnitIdCorruptionMode {
Fixed,
Increment,
Random,
Swap,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FcCorruptionMode {
Fixed,
Increment,
Random,
SwapRW,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TidCorruptionMode {
Fixed,
Increment,
Random,
SwapBytes,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TruncationMode {
FixedBytes,
Percentage,
RemoveLastN,
HeaderOnly,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ExtraDataMode {
AppendBytes,
AppendRandom,
DuplicateLastN,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PartialFrameMode {
FixedCount,
Percentage,
UpToFc,
UpToData,
}
impl FaultConfig {
pub fn crc_corruption(mode: CrcCorruptionMode, target: FaultTarget) -> Self {
Self {
fault_type: FaultType::CrcCorruption,
target,
config: FaultTypeConfig {
crc_mode: Some(mode),
..Default::default()
},
}
}
pub fn wrong_unit_id(mode: UnitIdCorruptionMode, target: FaultTarget) -> Self {
Self {
fault_type: FaultType::WrongUnitId,
target,
config: FaultTypeConfig {
unit_id_mode: Some(mode),
..Default::default()
},
}
}
pub fn wrong_function_code(mode: FcCorruptionMode, target: FaultTarget) -> Self {
Self {
fault_type: FaultType::WrongFunctionCode,
target,
config: FaultTypeConfig {
fc_mode: Some(mode),
..Default::default()
},
}
}
pub fn wrong_transaction_id(mode: TidCorruptionMode, target: FaultTarget) -> Self {
Self {
fault_type: FaultType::WrongTransactionId,
target,
config: FaultTypeConfig {
tid_mode: Some(mode),
..Default::default()
},
}
}
pub fn truncated_response(mode: TruncationMode, target: FaultTarget) -> Self {
Self {
fault_type: FaultType::TruncatedResponse,
target,
config: FaultTypeConfig {
truncation_mode: Some(mode),
..Default::default()
},
}
}
pub fn delayed_response(delay: Duration, jitter: Duration, target: FaultTarget) -> Self {
Self {
fault_type: FaultType::DelayedResponse,
target,
config: FaultTypeConfig {
delay_ms: Some(delay.as_millis() as u64),
jitter_ms: Some(jitter.as_millis() as u64),
..Default::default()
},
}
}
pub fn no_response(target: FaultTarget) -> Self {
Self {
fault_type: FaultType::NoResponse,
target,
config: FaultTypeConfig::default(),
}
}
pub fn exception_injection(exception_code: u8, target: FaultTarget) -> Self {
Self {
fault_type: FaultType::ExceptionInjection,
target,
config: FaultTypeConfig {
exception_code: Some(exception_code),
..Default::default()
},
}
}
pub fn extra_data(mode: ExtraDataMode, count: usize, target: FaultTarget) -> Self {
Self {
fault_type: FaultType::ExtraData,
target,
config: FaultTypeConfig {
extra_data_mode: Some(mode),
extra_count: Some(count),
..Default::default()
},
}
}
pub fn partial_frame(mode: PartialFrameMode, target: FaultTarget) -> Self {
Self {
fault_type: FaultType::PartialFrame,
target,
config: FaultTypeConfig {
partial_mode: Some(mode),
..Default::default()
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = FaultInjectionConfig::default();
assert!(config.enabled);
assert!(config.faults.is_empty());
}
#[test]
fn test_config_builder() {
let config = FaultInjectionConfig::new()
.with_fault(FaultConfig::crc_corruption(
CrcCorruptionMode::Invert,
FaultTarget::new().with_probability(0.1),
))
.with_fault(FaultConfig::no_response(
FaultTarget::new()
.with_unit_ids(vec![1])
.with_probability(0.5),
));
assert!(config.enabled);
assert_eq!(config.faults.len(), 2);
assert_eq!(config.faults[0].fault_type, FaultType::CrcCorruption);
assert_eq!(config.faults[1].fault_type, FaultType::NoResponse);
}
#[test]
fn test_fault_config_constructors() {
let fc = FaultConfig::delayed_response(
Duration::from_millis(1000),
Duration::from_millis(200),
FaultTarget::new(),
);
assert_eq!(fc.fault_type, FaultType::DelayedResponse);
assert_eq!(fc.config.delay_ms, Some(1000));
assert_eq!(fc.config.jitter_ms, Some(200));
let fc = FaultConfig::exception_injection(0x04, FaultTarget::new());
assert_eq!(fc.fault_type, FaultType::ExceptionInjection);
assert_eq!(fc.config.exception_code, Some(0x04));
}
#[test]
fn test_serde_roundtrip() {
let config = FaultInjectionConfig::new().with_fault(FaultConfig::crc_corruption(
CrcCorruptionMode::Zero,
FaultTarget::new()
.with_unit_ids(vec![1, 2])
.with_probability(0.25),
));
let json = serde_json::to_string_pretty(&config).unwrap();
let deserialized: FaultInjectionConfig = serde_json::from_str(&json).unwrap();
assert!(deserialized.enabled);
assert_eq!(deserialized.faults.len(), 1);
assert_eq!(deserialized.faults[0].fault_type, FaultType::CrcCorruption);
assert_eq!(deserialized.faults[0].target.unit_ids, vec![1, 2]);
assert!((deserialized.faults[0].target.probability - 0.25).abs() < f64::EPSILON);
}
}