///|
/// Counters for protocol throughput and error monitoring.
pub struct FrameMetrics {
mut frames_encoded : Int
mut frames_decoded : Int
mut information_frames : Int
mut supervisory_frames : Int
mut unnumbered_frames : Int
mut bytes_encoded : Int
mut bytes_decoded : Int
mut malformed_frames : Int
mut sequence_errors : Int
mut service_failures : Int
} derive(Eq, Debug)
///|
pub fn FrameMetrics::new() -> FrameMetrics {
{
frames_encoded: 0,
frames_decoded: 0,
information_frames: 0,
supervisory_frames: 0,
unnumbered_frames: 0,
bytes_encoded: 0,
bytes_decoded: 0,
malformed_frames: 0,
sequence_errors: 0,
service_failures: 0,
}
}
///|
pub fn FrameMetrics::record_encoded(self : FrameMetrics, frame : Frame) -> Unit {
self.frames_encoded += 1
self.bytes_encoded += encoded_frame_size(frame)
match frame.kind {
Information => self.information_frames += 1
Supervisory => self.supervisory_frames += 1
Unnumbered => self.unnumbered_frames += 1
}
}
///|
pub fn FrameMetrics::record_decoded(self : FrameMetrics, frame : Frame) -> Unit {
self.frames_decoded += 1
self.bytes_decoded += encoded_frame_size(frame)
match frame.kind {
Information => self.information_frames += 1
Supervisory => self.supervisory_frames += 1
Unnumbered => self.unnumbered_frames += 1
}
}
///|
pub fn FrameMetrics::record_malformed(self : FrameMetrics) -> Unit {
self.malformed_frames += 1
}
///|
pub fn FrameMetrics::record_sequence_error(self : FrameMetrics) -> Unit {
self.sequence_errors += 1
}
///|
pub fn FrameMetrics::record_service_failure(self : FrameMetrics) -> Unit {
self.service_failures += 1
}
///|
pub fn FrameMetrics::total_frames(self : FrameMetrics) -> Int {
self.frames_encoded + self.frames_decoded
}
///|
pub fn FrameMetrics::total_bytes(self : FrameMetrics) -> Int {
self.bytes_encoded + self.bytes_decoded
}
///|
pub fn FrameMetrics::error_count(self : FrameMetrics) -> Int {
self.malformed_frames + self.sequence_errors + self.service_failures
}
///|
pub fn FrameMetrics::success_rate(self : FrameMetrics) -> Float {
let attempts = self.total_frames() + self.error_count()
if attempts == 0 {
1.0
} else {
Float::from_int(self.total_frames()) / Float::from_int(attempts)
}
}
///|
/// Snapshot an evolving metrics object for export or health endpoints.
pub struct MetricsSnapshot {
frames : Int
bytes : Int
errors : Int
success_rate : Float
} derive(Eq, Debug)
///|
pub fn FrameMetrics::snapshot(self : FrameMetrics) -> MetricsSnapshot {
{
frames: self.total_frames(),
bytes: self.total_bytes(),
errors: self.error_count(),
success_rate: self.success_rate(),
}
}
///|
pub fn MetricsSnapshot::frames(self : MetricsSnapshot) -> Int {
self.frames
}
///|
pub fn MetricsSnapshot::bytes(self : MetricsSnapshot) -> Int {
self.bytes
}
///|
pub fn MetricsSnapshot::errors(self : MetricsSnapshot) -> Int {
self.errors
}
///|
pub fn MetricsSnapshot::success_rate(self : MetricsSnapshot) -> Float {
self.success_rate
}
///|
/// A trace event with a monotonic timestamp.
pub enum TraceKind {
FrameSent
FrameReceived
StateChanged
PointUpdated
ServiceStarted
ServiceCompleted
DiagnosticRaised
} derive(Eq, Debug)
///|
pub struct TraceEvent {
timestamp : Int
kind : TraceKind
detail : String
correlation : Int
} derive(Eq, Debug)
///|
pub fn TraceEvent::new(
timestamp : Int,
kind : TraceKind,
detail : String,
correlation? : Int = 0,
) -> TraceEvent {
{ timestamp, kind, detail, correlation }
}
///|
pub fn TraceEvent::timestamp(self : TraceEvent) -> Int {
self.timestamp
}
///|
pub fn TraceEvent::kind(self : TraceEvent) -> TraceKind {
self.kind
}
///|
pub fn TraceEvent::detail(self : TraceEvent) -> String {
self.detail
}
///|
pub fn TraceEvent::correlation(self : TraceEvent) -> Int {
self.correlation
}
///|
pub struct TraceLog {
events : Array[TraceEvent]
limit : Int
} derive(Debug)
///|
pub fn TraceLog::new(limit? : Int = 4096) -> Result[TraceLog, String] {
if limit < 1 {
Err("trace log limit must be positive")
} else {
Ok({ events: [], limit })
}
}
///|
pub fn TraceLog::push(self : TraceLog, event : TraceEvent) -> Unit {
self.events.push(event)
while self.events.length() > self.limit {
ignore(self.events.remove(0))
}
}
///|
pub fn TraceLog::len(self : TraceLog) -> Int {
self.events.length()
}
///|
pub fn TraceLog::all(self : TraceLog) -> Array[TraceEvent] {
self.events.copy()
}
///|
pub fn TraceLog::since(self : TraceLog, timestamp : Int) -> Array[TraceEvent] {
let result : Array[TraceEvent] = []
for event in self.events {
if event.timestamp() >= timestamp {
result.push(event)
}
}
result
}
///|
pub fn trace_kind_examples() -> Array[TraceKind] {
[
FrameSent,
FrameReceived,
StateChanged,
PointUpdated,
ServiceStarted,
ServiceCompleted,
DiagnosticRaised,
]
}
///|
/// Per-byte statistics useful for fixture and link diagnostics.
pub struct ByteStatistics {
length : Int
mut zeroes : Int
mut high_bit : Int
mut checksum : UInt
mut minimum : Int
mut maximum : Int
} derive(Eq, Debug)
///|
pub fn byte_statistics(data : Bytes) -> ByteStatistics {
let result = {
length: data.length(),
zeroes: 0,
high_bit: 0,
checksum: 0U,
minimum: if data.is_empty() {
0
} else {
255
},
maximum: 0,
}
for byte in data {
let value = byte.to_int()
if value == 0 {
result.zeroes += 1
}
if value >= 128 {
result.high_bit += 1
}
result.checksum = (result.checksum + value.reinterpret_as_uint()) &
0xffffffffU
if value < result.minimum {
result.minimum = value
}
if value > result.maximum {
result.maximum = value
}
}
result
}
///|
pub fn ByteStatistics::length(self : ByteStatistics) -> Int {
self.length
}
///|
pub fn ByteStatistics::zeroes(self : ByteStatistics) -> Int {
self.zeroes
}
///|
pub fn ByteStatistics::high_bit(self : ByteStatistics) -> Int {
self.high_bit
}
///|
pub fn ByteStatistics::checksum(self : ByteStatistics) -> UInt {
self.checksum
}
///|
pub fn ByteStatistics::minimum(self : ByteStatistics) -> Int {
self.minimum
}
///|
pub fn ByteStatistics::maximum(self : ByteStatistics) -> Int {
self.maximum
}
///|
/// CRC-16/IBM helper for gateways that wrap IEC APDUs in a framed channel.
pub fn crc16_ibm(data : Bytes) -> UInt {
let mut crc : UInt = 0xffffU
for byte in data {
crc = crc ^ byte.to_int().reinterpret_as_uint()
for _ in 0..<8 {
if (crc & 1U) != 0U {
crc = (crc >> 1) ^ 0xa001U
} else {
crc = crc >> 1
}
}
}
crc
}
///|
/// CRC-32 used by fixture manifests and deterministic transport tests.
pub fn crc32_ieee(data : Bytes) -> UInt {
let mut crc = 0xffffffffU
for byte in data {
crc = crc ^ byte.to_int().reinterpret_as_uint()
for _ in 0..<8 {
if (crc & 1U) != 0U {
crc = (crc >> 1) ^ 0xedb88320U
} else {
crc = crc >> 1
}
}
}
crc ^ 0xffffffffU
}
///|
/// A compact histogram for APDU length distribution.
pub struct LengthHistogram {
buckets : Map[Int, Int]
mut samples : Int
} derive(Debug)
///|
pub fn LengthHistogram::new() -> LengthHistogram {
{ buckets: {}, samples: 0 }
}
///|
pub fn LengthHistogram::observe(self : LengthHistogram, length : Int) -> Unit {
let bucket = if length < 16 {
16
} else if length < 32 {
32
} else if length < 64 {
64
} else if length < 128 {
128
} else {
256
}
match self.buckets.get(bucket) {
Some(value) => self.buckets[bucket] = value + 1
None => self.buckets[bucket] = 1
}
self.samples += 1
}
///|
pub fn LengthHistogram::samples(self : LengthHistogram) -> Int {
self.samples
}
///|
pub fn LengthHistogram::bucket(self : LengthHistogram, maximum : Int) -> Int {
match self.buckets.get(maximum) {
Some(value) => value
None => 0
}
}
///|
pub fn LengthHistogram::all(self : LengthHistogram) -> Array[(Int, Int)] {
let result : Array[(Int, Int)] = []
for bucket, count in self.buckets {
result.push((bucket, count))
}
result.sort_by((left, right) => left.0 - right.0)
result
}
///|
/// Record an APDU length observation.
pub fn observe_frame_length(histogram : LengthHistogram, frame : Frame) -> Unit {
histogram.observe(encoded_frame_size(frame))
}
///|
pub fn diagnostics_examples() -> Array[DiagnosticKind] {
diagnostic_kind_examples()
}