///|
/// Deterministic fault modes for protocol integration tests.
pub(all) enum FaultMode {
NoFault
DropResponse
CorruptCrc
CorruptLrc
WrongTransaction
BusyDevice
InvalidFunction
} derive(Debug, Eq)
///|
pub fn fault_mode_name(mode : FaultMode) -> String {
match mode {
NoFault => "none"
DropResponse => "drop-response"
CorruptCrc => "corrupt-crc"
CorruptLrc => "corrupt-lrc"
WrongTransaction => "wrong-transaction"
BusyDevice => "busy-device"
InvalidFunction => "invalid-function"
}
}
///|
/// Result of one deterministic simulation step.
pub(all) enum SimulationResult {
Delivered(Frame)
Dropped
Rejected(ModbusError)
}
///|
/// A portable protocol simulation with no operating-system I/O.
pub struct Simulation {
device : Device
mode : Mode
mut fault : FaultMode
mut clock : Int
metrics : ProtocolMetrics
audit : AuditLog
}
///|
pub fn Simulation::new(
device : Device,
mode : Mode,
) -> Result[Simulation, ModbusError] {
let metrics = match ProtocolMetrics::new() {
Ok(value) => value
Err(error) => return Err(error)
}
let audit = match AuditLog::new() {
Ok(value) => value
Err(error) => return Err(error)
}
Ok({ device, mode, fault: NoFault, clock: 0, metrics, audit })
}
///|
pub fn Simulation::device(self : Simulation) -> Device {
self.device
}
///|
pub fn Simulation::mode(self : Simulation) -> Mode {
self.mode
}
///|
pub fn Simulation::clock(self : Simulation) -> Int {
self.clock
}
///|
pub fn Simulation::set_fault(self : Simulation, fault : FaultMode) -> Unit {
self.fault = fault
if fault == BusyDevice {
self.device.set_busy(true)
} else {
self.device.set_busy(false)
}
}
///|
pub fn Simulation::fault(self : Simulation) -> FaultMode {
self.fault
}
///|
pub fn Simulation::advance(
self : Simulation,
ticks : Int,
) -> Result[Unit, ModbusError] {
if ticks < 0 {
Err(InvalidData)
} else {
self.clock += ticks
Ok(())
}
}
///|
/// Send one request through the selected fault model.
pub fn Simulation::request(
self : Simulation,
transaction_id : UInt16,
request : Frame,
) -> SimulationResult {
let request_bytes = encode_mode(self.mode, request)
let _ = self.metrics.record_request(request_bytes.length())
self.audit.record("request", self.mode, transaction_id, request, "sent")
match self.fault {
DropResponse => {
self.audit.record("device", self.mode, transaction_id, request, "dropped")
SimulationResult::Dropped
}
InvalidFunction => {
let response = exception_response(request.unit_id, 0x7F, 1)
self.audit.record(
"response",
self.mode,
transaction_id,
response,
"invalid-function",
)
SimulationResult::Delivered(response)
}
_ =>
match self.device.handle(request) {
Err(error) => {
let _ = self.metrics.record_error()
self.audit.record(
"device",
self.mode,
transaction_id,
request,
error_name(error),
)
SimulationResult::Rejected(error)
}
Ok(response) => {
let outcome = if self.fault == WrongTransaction && self.mode == Tcp {
"wrong-transaction"
} else {
"delivered"
}
let bytes = encode_mode(self.mode, response)
let exception = response.is_exception()
let _ = self.metrics.record_response(bytes.length(), 1, exception)
self.audit.record(
"response",
self.mode,
transaction_id,
response,
outcome,
)
SimulationResult::Delivered(response)
}
}
}
}
///|
pub fn Simulation::metrics(self : Simulation) -> ProtocolMetrics {
self.metrics
}
///|
pub fn Simulation::audit(self : Simulation) -> AuditLog {
self.audit
}
///|
pub fn Simulation::reset(self : Simulation) -> Unit {
self.clock = 0
self.fault = NoFault
self.device.set_busy(false)
self.audit.clear()
}
///|
/// A deterministic multi-request scenario.
pub(all) struct Scenario {
name : String
requests : Array[Frame]
mode : Mode
}
///|
pub fn Scenario::new(name : String, mode : Mode) -> Scenario {
{ name, requests: [], mode }
}
///|
pub fn Scenario::add(
self : Scenario,
request : Frame,
) -> Result[Unit, ModbusError] {
if self.requests.length() >= 1024 {
Err(CapacityExceeded)
} else {
self.requests.push(request)
Ok(())
}
}
///|
pub fn Scenario::length(self : Scenario) -> Int {
self.requests.length()
}
///|
pub fn Scenario::name(self : Scenario) -> String {
self.name
}
///|
pub fn Scenario::mode(self : Scenario) -> Mode {
self.mode
}
///|
pub fn Scenario::run(
self : Scenario,
simulation : Simulation,
) -> Array[SimulationResult] {
let out : Array[SimulationResult] = []
for index, request in self.requests {
out.push(simulation.request(index.to_uint16(), request))
let _ = simulation.advance(1)
}
out
}
///|
/// Count successful deliveries in a scenario result.
pub fn simulation_successes(results : Array[SimulationResult]) -> Int {
let mut count = 0
for result in results {
if result is Delivered(_) {
count += 1
}
}
count
}
///|
pub fn simulation_failures(results : Array[SimulationResult]) -> Int {
results.length() - simulation_successes(results)
}