///|
/// A coarse frame class useful for metrics and routing policies.
pub enum FrameClass {
ClassicData
ClassicRemote
CanFdData
SimulationError
}
///|
/// A concrete validation finding.
pub enum FrameViolation {
IdentifierRange
ClassicPayloadRange
FdPayloadRange
RemoteCarriesData
FdControlOnClassic
InvalidErrorShape
NonCanonicalDlc
} derive(Debug)
///|
/// The result of validating a normalized frame.
pub struct FrameReport {
valid : Bool
violations : Array[FrameViolation]
wire_bits : Int
stuffed_bits : Int
}
///|
/// Validate protocol and simulation invariants.
pub fn validate_frame(frame : Frame) -> FrameReport {
let violations : Array[FrameViolation] = []
let data_length = frame.data().length()
let identifier_limit : UInt = if frame.is_extended() {
0x1FFFFFFF
} else {
0x7FF
}
if frame.id() > identifier_limit {
violations.push(IdentifierRange)
}
match frame.protocol() {
Can20 => {
if data_length > 8 {
violations.push(ClassicPayloadRange)
}
if frame.bitrate_switch() || frame.error_state_indicator() {
violations.push(FdControlOnClassic)
}
}
CanFd => {
if data_length > 64 {
violations.push(FdPayloadRange)
}
if frame.kind() is Remote || frame.kind() is Error {
violations.push(FdControlOnClassic)
}
}
}
if frame.kind() is Remote && data_length != 0 {
violations.push(RemoteCarriesData)
}
if frame.kind() is Error &&
(frame.id() != 0 || data_length != 1 || frame.is_extended()) {
violations.push(InvalidErrorShape)
}
if frame.protocol() is CanFd && fd_length_from_dlc(frame.dlc()) < data_length {
violations.push(NonCanonicalDlc)
}
let raw_bits = encode_bits(frame)
let stuffed = stuff(raw_bits)
{
valid: violations.is_empty(),
violations,
wire_bits: frame_wire_bits(frame),
stuffed_bits: stuffed.length(),
}
}
///|
pub fn FrameReport::is_valid(self : FrameReport) -> Bool {
self.valid
}
///|
pub fn FrameReport::violations(self : FrameReport) -> Array[FrameViolation] {
self.violations.copy()
}
///|
pub fn FrameReport::wire_bits(self : FrameReport) -> Int {
self.wire_bits
}
///|
pub fn FrameReport::stuffed_bits(self : FrameReport) -> Int {
self.stuffed_bits
}
///|
/// Return whether a frame passes all normalized invariants.
pub fn frame_is_valid(frame : Frame) -> Bool {
validate_frame(frame).is_valid()
}
///|
/// Classify a frame for counters and dashboards.
pub fn classify_frame(frame : Frame) -> FrameClass {
if frame.is_error() {
SimulationError
} else if frame.protocol() is CanFd {
CanFdData
} else if frame.is_remote() {
ClassicRemote
} else {
ClassicData
}
}
///|
/// Return the maximum payload allowed by the protocol.
pub fn max_payload(protocol : Protocol) -> Int {
match protocol {
Can20 => 8
CanFd => 64
}
}
///|
/// Estimate transmission time in microseconds.
pub fn estimate_transmission_us(
frame : Frame,
arbitration_kbps : UInt,
data_kbps : UInt,
) -> UInt64 {
if arbitration_kbps == 0 || data_kbps == 0 {
return 0
}
let bits = frame_wire_bits(frame).to_uint64()
let rate = if frame.protocol() is CanFd && frame.bitrate_switch() {
data_kbps.to_uint64()
} else {
arbitration_kbps.to_uint64()
}
(bits * 1000 + rate - 1) / rate
}
///|
/// Count stuffed bits in a sequence without allocating the stuffed result.
pub fn count_stuffed_bits(bits : Array[Bool]) -> Int {
if bits.is_empty() {
return 0
}
let mut previous = bits[0]
let mut run = 1
let mut inserted = 0
for bit in bits[1:] {
if bit == previous {
run += 1
} else {
run = 1
previous = bit
continue
}
if run == 5 {
inserted += 1
run = 1
previous = !previous
}
}
inserted
}
///|
/// Return a stable arbitration-sorted copy.
pub fn sort_by_arbitration(frames : Array[Frame]) -> Array[Frame] {
let result = frames.copy()
result.sort_by((left, right) => compare_frames(left, right))
result
}
///|
/// Return the frame class as a stable text label.
pub fn frame_class_name(class : FrameClass) -> String {
match class {
ClassicData => "classic-data"
ClassicRemote => "classic-remote"
CanFdData => "can-fd-data"
SimulationError => "simulation-error"
}
}
///|
/// Return a diagnostic description for a validation finding.
pub fn frame_violation_message(violation : FrameViolation) -> String {
match violation {
IdentifierRange => "identifier exceeds the selected CAN identifier width"
ClassicPayloadRange => "classic CAN payload exceeds eight bytes"
FdPayloadRange => "CAN-FD payload exceeds 64 bytes"
RemoteCarriesData => "remote frames cannot carry payload bytes"
FdControlOnClassic => "CAN-FD control flags or frame kind used with CAN 2.0"
InvalidErrorShape => "simulation error frames must use id 0 and one byte"
NonCanonicalDlc => "DLC does not cover the payload length"
}
}